background

Recently, I have been reconstructing the COMPANY’s H5 project, which involves construction optimization. Due to some historical reasons, the packaging tool originally used in the project was cooking developed by ele. me team (the packaging based on Webpack has been stopped maintenance at present). If we continue to use it, first, the project has been relatively complex, and the current construction method takes a long time to package each time; Second, using a tool that is no longer maintained carries its own risks. In addition, this reconstruction also requires Vue1.0 to Vue2.0 framework upgrade, which involves a series of dependent packages (vue-style-loader, etc.) version compatibility problems. After a day of messing around with vue2 and VUex3, I upgraded the build tool directly to WebPackage 4.

Due to the business needs of the company (SEO, the page is mainly put), our project adopts the multi-page architecture. The online single-page application template based on VUE is officially provided with VUE-CLI. There are also many third-party templates, but there are not many multi-page templates for reference. It took me about two weeks to sort out the multi-page application template based on WebPack4 + vue2 + vuex3 by referring to some blog materials and documents, and I recorded it for my convenience to check in the future and share it with other students who need reference.

The core concepts of WebPack

Inspired by zero-configuration build tools such as Parcel, WebPack4 has made a lot of optimizations to move towards no-configuration. Although zero-configuration is supported, you still need to manually set some configuration items if you want fine-grained control over your modules. But compared to previous versions of WebPack, it has been significantly simplified and much easier to get started. Here are the core configuration items of WebPack4, which will be expanded one by one:

  • mode
  • entry
  • output
  • loader
  • plugins
  • devServer

Next, I will follow the above order and try to list in detail the whole process of building vue2 and VUex multi-page applications based on WebPack4

mode

Webpack4 new, specify the packaging mode, the optional values are:

  1. Development
    • Process.env.node_env is set to development
    • Enable NamedChunksPlugin and NamedModulesPlugin
  2. Production D
    • Process.env.node_env is set to production
    • Maximized optimization (compression of modules, concatenation, etc.) is enabled
  3. None. This mode is not optimized

Mode Setting mode:

  • Package. json is set in shell command parameters
webpack --mode=production
Copy the code
  • Configure the mode configuration item
module.exports = {
  mode: 'production'
};
Copy the code

For more information, see the official document Mode

entry

The biggest difference between a multi-page app and a single page app (SPA) is the entry point

  • Multi-page: The final package generates multiple entries (HTML pages), generally each entry file in addition to the introduction of a common static file (JS/CSS) but also the introduction of page-specific static resources
  • Single page: There is only one entry (index.html), all the static files need to be packed into the page, and all the page content is controlled by JavaScript

It should be noted that the entry mentioned above refers to the HTML file that is finally packaged into the dist directory. The entry we configured here is actually the JS module that needs to be introduced by HTML. These js modules, along with the removed common JS modules, eventually need to be combined into an HTML file using the htMl-webpack-plugin:

const config = require('./config'); // Multi-page configuration items
let HTMLPlugins = [];
let Entries = {};

config.HTMLDirs.forEach(item= > {
  let filename = `${item.page}.html`;
  if (item.dir) filename = `${item.dir}/${item.page}.html`;
  const htmlPlugin = new HTMLWebpackPlugin({
    title: item.title, // The title of the generated HTML page
    filename: filename, Dist (${item.page}/index.html); dist (${item.page})
    template: path.resolve(__dirname, `.. /src/template/index.html`), // Template file, different entry can be set according to the needs of different templates
    chunks: [item.page, 'vendor'].// The js module to be imported in the HTML file, where vendor is the name of the public module removed under the default configuration of Webpack
  });
  HTMLPlugins.push(htmlPlugin);
  Entries[item.page] = path.resolve(__dirname, `.. /src/pages/${item.page}/index.js`); // Set the entry js file according to the configuration
});
// ...


Copy the code

Multi-page configuration information in config.js:

module.exports = {
  HTMLDirs: [{page: 'index'.title: 'home'
    },
    {
      page: 'list'.title: 'List page'.dir: 'content' // Multi-level directories are supported

    },
    {
      page: 'detail'.title: 'Details Page'}].// ...
};
Copy the code

Finally, introduce the relevant configuration:

module.exports = {
  entry: Entries,
  // ...
   plugins: [
     ...HTMLPlugins // Use HTMLWebpackPlugin to synthesize the final page
   ]
  // ... 
}
Copy the code

The decoupling of public modules will be covered separately

Html-webpack-plugin plugin is the official website of html-webpack-plugin

output

Configuration exit file name and path:

const env = process.env.BUILD_MODE.trim();
let ASSET_PATH = '/'; / / dev environment
if (env === 'prod') ASSET_PATH = '//abc.com/static/'; // Build to the actual static service address
module.exports = {
  entry: Entries,
  output: {
    publicPath: ASSET_PATH,
    filename: 'js/[name].[hash:8].js'.path: path.resolve(__dirname, '.. /dist'),}}Copy the code

The generated JS file is attached with an 8-bit MD5 stamp to take full advantage of the CDN cache.

For the hash calculation methods and differences, see the differences of Hash, Chunkhash, and Contenthash in Webpack

loader

Loader is used to convert the source code of the module, and is responsible for converting the content of a certain file format into packaged modules that WebPack can support, for example, converting sASS preprocessing into CSS modules; Convert TypeScript to JavaScript; Or convert an inline image to a data URL

Specific configuration:

  • Webpack.base.js (base configuration file)
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// ...

module: {
  rules: [{test: /\.vue$/.// Process the vUE module
      use: 'vue-loader'}, {test: /\.js$/.// Handle es6 syntax
      exclude: /node_modules/.use: ['babel-loader'],}, {test: /\.(png|svg|jpg|gif)$/.// Process the image
      use: {
        loader: 'file-loader'.// Resolve the problem that image paths cannot be resolved in packaged CSS files
        options: {
          // The name of the generated image
          name: '[name].[ext]'.// Image generation path
          outputPath: config.imgOutputPath,
        }
      }
    },
    {
      test: /\.(woff|woff2|eot|ttf|otf)$/.// Handle fonts
      use: {
        loader: 'file-loader'.options: {
          outputPath: config.fontOutputPath,
        }
      }
    }
  ]
},
  plugins: [
    // ...
    new VueLoaderPlugin()  
  ]
// ...
Copy the code

Vue-loader is used together with the VueLoaderPlugin plug-in. Babel-loader is used with. Babelrc. Here “stage-2” is configured to use the advanced syntax in ES7. Without this configuration, new syntax features such as object extenders, async and await cannot be handled.

Babelrc configuration:

{
  "presets": [["env", {
      "modules": false."targets": {
        "browsers": ["1%" >."last 2 versions"."not ie <= 8"]}}],"stage-2"]."plugins": ["transform-runtime"]}Copy the code

For configuration related to.babelrc, see: official documentation; Babel configuration – The difference between stages

  • Webpack.dev.js (development configuration file)
// ...
module: {
  rules: [{test: /\.css$/.exclude: /node_modules/.use: [
        'vue-style-loader'.// Handle CSS styles in vue files
        'css-loader'.'postcss-loader',]}, {test: /\.scss$/.exclude: /node_modules/.use: [ // These loaders process styles from right to left
        'vue-style-loader'.'css-loader'.'sass-loader'.'postcss-loader',
        { 
          loader: 'sass-resources-loader'.// Pack defined sass variables, mix, and other uniform styles into each CSS file, avoiding manual introduction on each page
          options: {
            resources: path.resolve(__dirname, '.. /src/styles/lib/main.scss'),}}]}, {test: /\.(js|vue)$/.enforce: 'pre'.// Force ESLint checking first
      exclude: /node_modules|lib/.loader: 'eslint-loader'.options: {
        // Enable automatic repair
        fix: true.// Enable the warning message
        emitWarning: true,}}]},// ...
Copy the code
  • Webpack.prod.js (production configuration file)
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ASSET_PATH = '//abc.com/static/'; // Static resource address online
// ...
module: {
  rules: [{test: /\.css$/.exclude: /node_modules/.use: [
        MiniCssExtractPlugin.loader,
        'css-loader'.'postcss-loader'] {},test: /\.scss$/.exclude: /node_modules/.use: [
        MiniCssExtractPlugin.loader,
        'css-loader'.'sass-loader'.'postcss-loader',
        {
          loader: 'sass-resources-loader'.options: {
            resources: path.resolve(__dirname, '.. /src/styles/lib/main.scss'),},}]}, {test: /\.(png|svg|jpg|gif)$/.// Process the image
        use: {
          loader: 'file-loader'.// Resolve the problem that image paths cannot be resolved in packaged CSS files
          options: {
            // The name of the generated image
            name: '[name].[hash:8].[ext]'.// Image generation path
            outputPath: config.imgOutputPath,
            publicPath: ASSET_PATH
          }
        }
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/.// Handle fonts
        use: {
          loader: 'file-loader'.options: {
            outputPath: config.fontOutputPath,
            publicPath: ASSET_PATH
          }
        }
      }
  ]
},
// ...
plugins: [
  new MiniCssExtractPlugin({
    filename: 'css/[name].[chunkhash:8].css' // CSS is finally detached as a single file to the dist/ CSS directory})]Copy the code

The extract-text-webpack-plugin used to extract CSS into a single file no longer supports WebPack4, the mini-CSs-extract-plugin is now available to handle CSS extraction

plugins

In the Webpack packaging process, the module code conversion work is handled by loader, other work can be completed by Plugin. Commonly used are:

  • Uglifyjs-webpack-plugin, handle JS code compression
  • Mini-css-extract-plugin, extract CSS into a single file
  • Clean-webpack-plugin, used to clean up the DIST folder at each build
  • Copy -webpack-plugin, copy file
  • Webpack HotModuleReplacementPlugin, heat load
  • Webpack.defineplugin, which defines environment variables

Specific configuration:

  • Webpack.base.js (base configuration file)
const HTMLWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
// ...
plugins: [
  new VueLoaderPlugin(),
  new CopyWebpackPlugin([
    {
      from: path.resolve(__dirname, '.. /public'),
      to: path.resolve(__dirname, '.. /dist'),
      ignore: ['*.html'] {},from: path.resolve(__dirname, '.. /src/scripts/lib'), // Move local library resources
      to: path.resolve(__dirname, '.. /dist')}]),... HTMLPlugins,// Use HTMLWebpackPlugin to synthesize the final page
  new webpack.DefinePlugin({
    'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH) // Use process.env.asset_path to ensure that the template file references the correct static resource address})]Copy the code
  • Webpack.prod.js (production configuration file)
// Extract CSS extract-text-webpack-plugin no longer supports Webpack4, the mini-css-extract-plugin is now available to handle CSS extraction
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
  // Automatically clean up the dist folder
  new CleanWebpackPlugin(['dist'] and {root: path.resolve(__dirname, '.. '),
    verbose: true.// Enable output information on the console
    dry: false,}).new MiniCssExtractPlugin({
    filename: 'css/[name].[chunkhash:8].css'})]Copy the code

devServer

In daily development, we need to start a static server locally to facilitate development and debugging. We use webpack-dev-server, an official provided tool, to quickly start a static service based on the current Webpack build configuration. When the mode of development, will have the hot reload function, so no longer need to manually introduced webpack. HotModuleReplacementPlugin plug-in.

Install webpack-dev-server as a development dependency and start it with NPM scripts:

npm install webpack-dev-server -S
Copy the code

Scripts configuration in package:

"scripts": {
  "dev": "cross-env BUILD_MODE=dev webpack-dev-server ",
},

Copy the code

For details about devServer configuration, see the official document dev-server

SplitChunks configuration

Webpack 4 CommonsChunkPlugin removed, replaced by two new configuration items (optimization) splitChunks and optimization) runtimeChunk) is used to extract the public js module. Through optimization. RuntimeChunk: true options, webpack will add a runtime (runtime) contains only additional blocks of code to each entry. (Note: This is scenario-dependent and causes each entry to load an extra copy of the runtime code).

SplitChunks default configuration description:

module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: 'async'.// Controls which code blocks WebPack chooses to split (other types of code blocks are packed the default way). There are three optional values: Initial, async, and all.
      minSize: 30000.// Form the smallest size of a new code block
      maxSize: 0.minChunks: 1.// The minimum number of times the code block should be referenced before splitting (the default policy is to split without multiple references)
      maxAsyncRequests: 5.// The maximum number of code blocks loaded on demand should be less than or equal to 5
      maxInitialRequests: 3.// The maximum number of code blocks initially loaded should be less than or equal to 3
      automaticNameDelimiter: '~'.name: true.cacheGroups: {
        vendors: { // Assign all modules from node_modules to a cache group called vendors
          test: /[\\/]node_modules[\\/]/.priority: - 10 // Cache group priotity is negative, so all custom cache groups can have a higher priority than this
        },
        default: { 
          minChunks: 2.// All code that is referenced twice or more is assigned to the default cache group.
          priority: - 20.// A module can be assigned to multiple cache groups. The optimization policy assigns modules to the cache group with the highest priority
          reuseExistingChunk: true // Allows reuse of existing code blocks, rather than creating new ones, only when the module matches exactly.}}}}};Copy the code

For details about SplitChunksPlugin configuration, see the official document SplitChunksPlugin

Vue && Vuex

Vue:

We know that the vUE single-page application has only one entry. The default entry file is main.js, where the VUE template is processed, the Vuex finally constructs the Vue object. A multi-page application has multiple entries, which means that each entry handles the same thing main.js does on a single page. The general configuration looks something like this:

import Vue from 'vue';
import Tpl from './index.vue'; / / the Vue template
import store from '.. /.. /store'; // Vuex

new Vue({
  store,
  render: h= > h(Tpl),
}).$mount('#app');
Copy the code

Vuex:

The store is divided into multiple modules in order to avoid that all states are concentrated in the Store object, resulting in a bloated file that is difficult to maintain. Each module has its own state, mutation, and action. At the same time, extract the getter into a separate file. The file structure is as follows:

|- store
|   |-modules
|   |   |-app.js / / a single module
|   |   |-user.js // // Single Module
|   |-getters.js    
|   |-index.js // Organize the modules here
Copy the code

The Settings for a single module are as follows:

const app = {
  state: { // state
    count: 0
  },
  mutations: { // mutations
    ADD_COUNT: (state, payload) = >{ state.count += payload.amount; }},actions: { // actions
    addCount: ({ commit }, payload) = > {
      commit('ADD_COUNT', {
        amount: payload.num }); }}};export default app;
Copy the code

Finally assemble each module in index.js:

import Vue from 'vue';
import Vuex from 'vuex';
import app from './modules/app';
import user from './modules/user';
import getters from './getters';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    app,
    user
  },
  getters
});

export default store;
Copy the code

conclusion

Finally finished, filled in a lot of holes in the middle, but all the way down there is still a lot of harvest, later there is time to continue to improve. The github address of the project source code is here: webpack4-vue2-multipage, there is a need to directly take, if you have some help, also please give a star ha ~~

The resources

  • Official wepack documentation
  • Webpack4 incomplete migration indicates north
  • Multi-page application solution based on Webpack4 + Vue
  • Without the CommonsChunkPlugin, how can we subcontract?
  • Hash, Chunkhash, contenthash differences in webpack
  • Webpack 4 finally knows that convention is better than configuration
  • What are the 7 SourceMap modes in devtool?