Webpack4 + Vue multi-page project fine construction ideas

The original link: zhangzippo. Making. IO/posts / 2019 /…

Although front-end projects are currently dominated by single pages, multiple pages are not all bad and can be useful in some situations, such as:

  1. Projects are large and business modules need to be decoupled
  2. SEO is easier to optimize
  3. There are no complex state management issues
  4. Can achieve a separate page online

preface

Here is some explanation for the fourth point and a valuable thought for the multi-page application scenario. In a project within the group, due to the expansion of the project, it was difficult to split the system, and the project page reached more than 200+, so the construction speed was very slow and the deployment time was very long. Often due to copywriting changes and some simple bug fixes have to be rebuilt. If you use a single page, on the one hand, the deployment time will increase with the volume, on the other hand, it is difficult to break down the project. Multiple page at this time there is a kind of advantage, we can do it on the front end menu contains only an empty frame, content area adopts multiple page structure, when we deploy online can be online, only on a single page speed greatly enhance internal can be integrated front-end routing (single page), so that business can be smooth decoupling between modules.

The project architecture

Vue-cli is not used in VUE + typescript + WebPack4 vUE projects because it is important for developers to understand the detailed process of construction. The purpose of vuE-CLI tools is to quickly build projects so that developers can quickly take over and get into business code writing. So there’s a lot of implicit work done for us, a lot of build and local development optimizations, etc., but it’s very helpful for developers to know every step and every detail of what they’re doing (especially since a lot of programmers on the team don’t want to use highly packaged stuff).

Train of thought

For multiple pages, comparing with a single page is nothing more than the following problems:

  1. Entry entry file for multiple, need to consider the page need to be generated automatically, less pre-set a few can be.
  2. HtmlWebpackPlugin also needs to be added when it is used.
  3. Public static resource extraction issues, whether splitChunkPlugin needs to be used.
  4. Finally, the implementation of the functionality that supports part of the construction of the project

To achieve our ultimate goal, also is able to build part of the code, we will be a project from a perspective of business division, two level, module and page, module represents a specific business scenarios, each page page on behalf of this business scenario, we will support for single/multiple modules and single/multiple packaging of the page.

start

Let’s start by looking at our project directory structure:

├─ Build_tasks // Build Scripts

├─ Config // Config file

├─ SRC // source code path

├ ── garbage //build

The SRC directory:

src

├─ Global.js // Project Global Tools

├─ Modules //

│ ├ ─ ─ Layout. Vue

│ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ └

│ │ ├ ─ ─ xx. Js

│ │ ├ ─ ─ index. Vue

Automatic Entry Generation

Since we have so many pages, we definitely need to automatically generate entry files, and this step needs to be done before entering the WebPack build process. Let’s create a build_entries.ts file to write the entry creation process, and put some core code here


const getTemplate = pagePath= > {
  return (
  `
import App from '${pagePath}'; import Vue from 'vue'; new Vue({ render: function (h) { return h(App); } }).$mount('#app'); `);
}
const scriptReg = /
      ([\s\s]*?)>;
/** * Determine which suffix the file should use
const getSuffix = (source: string): string= > {
  const matchArr = source.match(scriptReg) || [];
  if(matchArr[1].includes('ts')) {return '.ts'
  }
  return '.js';
};

const generateEntries = (a)= > {
  const entries = {};
  /*** Get pages*/
  if(! pages.length)return entries;
  / / remove entries
  rimraf.sync(entryPath+'/ *. *');
 
  pages.forEach(page= > {
    const relativePage = path.relative(vueRoot, page);
    const source = fs.readFileSync(page, 'utf8');
    const suffix = getSuffix(source);
    const pageEntry = path.resolve(entryPath, relativePage.replace(/\/index\.vue$/.' ').replace(/\//g.'. ')) + suffix;
    const entryName = path.basename(pageEntry, suffix);
    entries[entryName] = pageEntry
    if (fs.existsSync(pageEntry)) return;
    const pagePath = path.resolve(vueRoot, relativePage);
    const template = getTemplate(pagePath);
    fs.writeFileSync(pageEntry, template, 'utf-8');
  });
  return entries
}

export const getEntriesInfos = (a)= >{
  return generateEntries();
}

Copy the code

We specify modules/xxmodle/ xxPage for the project directory structure, we use the page named index.vue as the entry page, create js templates for each index.vue entry (getTemplete method), and generate an entry named “module name”. The page name is.js. Since the project needs to support TS, we also need to determine the language of script tags in vUE to create TS or JS entries. Our WebPack configuration:

const entries  = getEntriesInfos();
const common = {
  entry: entries,
  output: {
    filename: `[name]-[hash].bundle.js`.path: path.resolve(rootPath, 'static'),
    publicPath,
  },
Copy the code

Public file extraction

Because we are multi-page, each page needs to load the core package (such as Vue, Element-UI, Lodash, etc.) and this kind of package we do not change often, so we need to use webPack dllPlugin to strip them out and do not participate in the construction. We may also have our own global toolkit in our project. This part of code is not suitable for extraction. We just need to add another entry of Common into the entry. Whether splitchunk should be used within a single page is not used in my practice, but it depends. If the package referenced by the page is really large (after all, framework packages like VUE have been extracted, which is unlikely) then splitchunk can be used to separate the page. My current practice is to merge into a single page js, single page JS is tolerable under 200K after gzip. Here is the DLL configuration webpack.dll.config.ts

const commonLibs = ['vue'.'element-ui'.'moment'.'lodash']

export default {
  mode: 'production'.entry: {
    commonLibs
  },
  output: {
    path: path.join(__dirname, 'dll_libs'),
    filename: 'dll.[name].[hash:8].min.js'.library: '[name]'.// publicPath: '/static/'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      context: __dirname,
      path: path.join(__dirname, 'dll_libs/'.'[name]-manifest.json'),
      name: '[name]'
    }),
    new assetsWebpackPlugin({
      filename: 'dll_assets.json'.path: path.join(__dirname,'assets/')]}})as webpack.Configuration;
Copy the code

As shown in the code we extract ‘vue’,’element-ui’,’moment’, and ‘lodash’ into a common package called commonLib, which uses the assetsWebpackPlugin to generate a JSON file. Record the filename of each DLL build (since the hash is different each time) in order to get the result injected into the template page when using the WebPackhtmlPlugin. The resulting JSON record looks like this:

{"commonLibs": {"js":"dll.commonLibs.51be3e86.min.js"}}
Copy the code

So we can get this name in the Webpack configuration file:

const dllJson = require('./assets/dll_assets.json');
for(let entryKey in entries){
  if(entryKey! = ='global'){
    common.plugins.push(
      new HtmlWebpackPlugin({
        title: allConfiguration[entryKey].title,
        isDebug: process.env.DEBUG,
        filename: `${entryKey}.html`,
        template: 'index.html',
        chunks:['global', entryKey, ],
        chunksSortMode: 'manual', dll_common_assets: process.env.NODE_ENV ! = ='production'?'./dll_libs/' + dllJson.commonLibs.js : publicPath + 'dll_libs/' + dllJson.commonLibs.js,
      
      }),
    )
  }
}
Copy the code

Because it is multiple pages, so we use webpackHTML also need to add multiple, here according to the generated JSON to get the filename of the DLL into the template page.

According to the need to pack

Next we need to support on-demand build packaging, single/multi-module and single/multi-page packaging. How to do this, we can pass in environment variables at build time, and then determine environment variables in build_entry for local packaging, because the entry to packaging is determined by the number of entries. Commands can be structured like this:

 MODULES=xxx,xxx PAGES=sss,sss npm run build
Copy the code

Build_entry code, in the generateEntries method

const entries = {};
  const buildModules = process.env.MODULES || The '*';
  const buildPages = process.env.PAGES || The '*';
  const filePaths = `The ${! buildModules.includes(',') ? buildModules : '{'+buildModules+'}'} / ${! buildPages.includes('.')? buildPages : '{'+buildPages+'}'}/*.vue` const pages = glob.sync(path.resolve(vueRoot, filePaths)).filter(file =>{ return /index\.vue$/.test(file) || []; }) if (! pages.length) return entries;Copy the code

The above method spells the corresponding page and module paths based on the environment variables passed in, and builds by generating the corresponding entyr with the support of the glob.

Multi-page online publishing

Multi-page build is release process after complete, release process also will become simple, actually every time if it is a single page is built to replace static files (js, CSS), overall, multi-page mode, we only need to replace the corresponding page files, the general idea is page files can be uploaded to the deployment server, and then static js, CSS and other files can be directly thrown into the CDN, the release will not affect other pages, even if the error will not affect the project, and high efficiency, this part of the code is not shown, just to provide ideas, after all, each project release process is not quite the same.

conclusion

The above is my idea of multi-page application scenarios. It has certain applicable scenarios and is more suitable for large and complete systems with clear module division.