Thoma Biguères
Thoma Biguères

Reputation: 1136

Having Vue Components as entry point instead of main.js

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 ?

Upvotes: 3

Views: 2757

Answers (1)

Chris Calo
Chris Calo

Reputation: 7838

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');
  `;
}

Upvotes: 4

Related Questions