3

I'm working with a Java backend and Jersey and want to have the possibility to have different small page app. My idea was to have a frontend module in which I would have a folder /apps. The folder apps would then contain multiple vue components (that will be the main apps). Another folder /component contains the different components that will be used in the different apps. The idea is to have a webpack that would create one js file per vue app !

I know that Webpack is not specially designed to have multiple entrypoint / multiple outputs but does anyone have any idea how I could have multiple entry points being the different apps-file.vue and having multiple .js files as output ?

Chris Calo
  • 7,518
  • 7
  • 48
  • 64
Thoma Biguères
  • 1,136
  • 4
  • 18
  • 42
  • 1
    You should probably remove `java` and `jersey`, because they are part of your server but your question is about client, so they are not really related to this question. – BackSlash Oct 05 '18 at 14:50
  • 1
    See the second answer here: https://stackoverflow.com/questions/35903246/how-to-create-multiple-output-paths-in-webpack-config You'd still to have a main js file for each app as the entry points to load the Vue components. – thanksd Oct 05 '18 at 14:53

1 Answers1

4

I had a similar problem and this answer pointed me in the right direction: I followed the Vue CLI docs to add a pages configuration option in a vue.config.js file.

After some experimentation, I got to a solution I was happy with. I saved it in repo that describes step-by-step how to create a multi-page Vue app from scratch.

https://github.com/chriscalo/vue-multipage

Some of the main things I was looking for:

  1. Uses the Vue CLI, which means you can avoid most webpack configuration headaches.
  2. Doesn't force you to create an entry point .js file for each app / page.

With this approach, you just place a bunch of .vue files in the src/pages directory and it generates a separate Vue app for each one. (You can pretty easily change that folder name from pages to apps if you would like.)

Autogenerate pages config and entry points

The crux of the solution is a script that creates a src/entry/bar/index.js entry point file for every src/pages/bar.vue file found and also generates a src/entry/pages.config.js file that you can import into your vue.config.js file like so:

const pagesConfig = require("./src/entry/pages.config.js");

module.exports = {
  pages: pagesConfig,
};

Here's the script:

const path = require("path");
const glob = require("fast-glob");
const fse = require("fs-extra");
const R = require("ramda");
const { stripIndent } = require("common-tags");

const pathGlob = processRelativePath("../src/pages/**/*.vue");
const vuePagesPromise = glob(pathGlob);

console.log(`Generating entry points`);

// Step 1: compute specifications for work to be done
const pagesConfigPromise = vuePagesPromise.then(pages => {
  return pages.map(page => {
    const { dir, name } = path.parse(page);
    const entryRoot = path.relative("src/pages", dir);
    const entryName = (
      split(entryRoot, path.sep)
    ).concat(
      ensureEndsWith([name], "index")
    ).join(path.sep);
    const entryFilePath = path.join(
      processRelativePath("../src/entry"), `${entryName}.js`
    );
    const importPath = path.relative("src", page);
    const entryFileContent = entryPointContent(importPath);

    return {
      source: page,
      entryName,
      entryFilePath,
      entryFileContent,
    };
  });
});

// Step 2: clear entry folder
const entryFolderPath = processRelativePath("../src/entry");
fse.removeSync(entryFolderPath);
console.log(`Cleared ${entryFolderPath}`);

// Step 3: create a corresponding entry point file for each page
pagesConfigPromise.then(config => {
  config.forEach(page => {
    fse.outputFileSync(page.entryFilePath, page.entryFileContent);
    console.log(`Created ${page.entryFilePath}`);
  });
});

// Step 4: create a pages.config.js
// module.exports = {
//   "index": 'src/pages/index.js',
//   "login/index": "src/pages/login.js",
//   "profile/index": "src/pages/profile/index.js",
//   "foo/index": 'src/pages/foo.js',
//   "bar/index": 'src/pages/bar/index.js',
// };
const pagesConfigPath = processRelativePath("../src/entry/pages.config.js");
pagesConfigPromise
  .then(config => {
    // transforms each into something like:
    // { "login/index": "src/pages/login.js" }
    return config.map(page => ({
      [page.entryName]: page.entryFilePath,
    }));
  })
  .then(R.mergeAll)
  .then(pageConfigContent)
  .then(content => fse.outputFileSync(pagesConfigPath, content))
  .then(() => console.log(`Created ${pagesConfigPath}`));


function pageConfigContent(config) {
  return stripIndent`
    module.exports = ${JSON.stringify(config, null, 2)};
  `;
}

function processRelativePath(p) {
  const pathToThisDir = path.relative(process.cwd(), __dirname);
  return path.join(pathToThisDir, p);
}

// fixes split() behavior for empty string ("")
function split(string, separator) {
  if (string.length === 0) {
    return [];
  } else {
    return string.split(separator);
  }
}

function ensureEndsWith(array, item) {
  if (array.slice(-1)[0] === item) {
    return array;
  } else {
    return array.concat([item]);
  }
}

function entryPointContent(importPath) {
  return stripIndent`
    import Vue from "vue";
    import page from "@/${importPath}";

    new Vue({
      render: h => h(page),
    }).$mount('#app');
  `;
}
Chris Calo
  • 7,518
  • 7
  • 48
  • 64
  • Wow, great script. But is there no simpler solution to this? i.e. A webpack plugin I can install that allows wraps the .vue entry module in an vue render? – Junaga Nov 10 '20 at 22:42
  • 1
    I feel your pain. I really wish Vue would solve this problem, but it doesn't seem like they see file-based routing as something the core Vue library should solve. Nuxt.js is alternative that has file-based routing, you might give that a look. – Chris Calo Nov 13 '20 at 18:21