1. Introduction:

Let’s start with a brief introduction to WebPack: the static module packaging tool for modern JavaScript applications. When WebPack works with an application, it builds a dependency graph internally that maps each module needed by the project and generates one or more bundles. With the advent of webpack4.0, we no longer need to introduce a configuration file to package a project, and it is still very configurable for our needs. Before we start, let’s look at what we want to achieve:

  • Support ES6+JQuery+Less/Scss single-page/multi-page scaffolding
  • Support for ES6+React+Less/Scss+Typescript single-page/multi-page scaffolding
  • Support for ES6+Vue+Less/Scss+Typescript single-page/multi-page scaffolding

Making address:

React /vue/typescript/es6+/jquery+less/ SCSS

During the scaffolding development PROCESS, I will detail the use of each plug-in or Loader and the core concepts of WebPack. If you don’t understand or have other better ideas, please feel free to communicate. Here’s a webpack4.0 mind map based on this article:

2. Webpack core concepts

  • Entry: Indicates which module webPack should use as the starting point for entry
  • Output: Tells WebPack where to output the bundles it creates and how to name the files
  • Loader: Enables WebPack to process other types of files and convert them into valid modules for use by applications
  • Plug-ins: Used to perform a wider range of tasks. These include: packaging optimization, resource management, injecting environment variables
  • Mode: Set the mode parameter by selecting one of development, Production, or None for different packaging optimizations
  • Browser compatibility: All browsers that meet ES5 standards are supported (IE8 and later versions are not supported).

The packaging model for the official website is provided below

Support ES6+JQuery+Less/Scss single-page/multi-page scaffolding

Before implementing the scaffolding, assuming we have created the directory and package.json file, let’s install the WebPack dependencies:

You are advised to install local dependencies. Installing global dependencies may cause version problems
npm install -D webpack webpack-cli
Copy the code

Because the project will support ES6 +, we also need to install Babel dependencies:

npm install -D babel-loader @babel/core @babel/preset-env 
Copy the code

At this point, we can start to configure our scaffolding logic. In order to make the project structure clear and easy to maintain, we will create a build directory dedicated to the webPack build script. The default configuration file for WebPack is webpack.config.js. These are webpack general configuration file webpack.base.js, development environment configuration file webpack.dev.js, and production environment configuration file webpack.prod.js.Let’s start with the webpack.mon.js file

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        main: './src/index.js',},output: {
        path: path.resolve(__dirname, '.. /dist'),},module: {
        rules: [
            // Compile es6 into ES5
            { 
                test: /\.jsx? $/./ /? That means that x has zero or one
                exclude: /node_modules/.// Do not compile files in a directory
                include: path.resolve(__dirname, '.. /src'),  // Compile the loader only in the included directory
                use: [
                    "babel-loader",]},]}}Copy the code

For the later development and maintenance of the project, we established the project structure:

The public directory is a prepared HTML template that will not be covered here, but other directories can be set according to the specific project.

We also need a plug-in to implant the packaged files into the HTML template and export them to the dist directory, using the HTml-webpack-plugin

npm install -D html-webpack-plugin
Copy the code

Now webpack.base.js is as follows:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const template = path.resolve(__dirname, '.. /public/index.html');

module.exports = {
    entry: {
        main: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, '.. /dist')},module: {
        rules: [
            // Compile es6 into ES5
            { 
                test: /\.jsx? $/./ /? That means that x has zero or one
                exclude: /node_modules/.// Do not compile files in a directory
                include: path.resolve(__dirname, '.. /src'),  // Compile the loader only in the included directory
                use: [
                    "babel-loader",]},]},plugins: [
        new HtmlWebpackPlugin({
            template,
            filename: 'index.html'}})]Copy the code

To package the project, we need to configure it in the webpack.prod.js directory. Here we need a module webpack-merge to merge the wepack base configuration into the production configuration.

npm install -D webpack-merge
Copy the code

The webpack.prod.js configuration is as follows:

const merge = require('webpack-merge');
const base = require('./webpack.base');

module.exports = merge({
    mode: 'production'.output: {
        filename: 'js/[name]_[contenthash].js'.// Entry and content hash file name, can also be hash
		chunkFilename: 'js/[name]_[contenthash].chunk.js'
    }
}, base)
Copy the code

Then add the execution script in package.json:

"scripts": {
    "build": "webpack --config ./build/webpack.prod.js"
  }
Copy the code

By default, WebPack will find a file named webpack.config.js. Since we split it into prod and dev, we need to manually specify the file that WebPack executes. Add –config to manually specify the directory. Let’s start by writing the following es6 code in index.js:

// index.js
let name = 'xuxi';
let say = (name) = > {
    alert(name)
};
say(name);
Copy the code

Here we execute:

npm run build
Copy the code

At this point we’ll see an additional dist directory in the project, with the HTML embedded with the code,Open in your browser:Ok, step one is complete. The next step to support CSS is to install the following modules:

npm install --save-dev css-loader style-loader
Copy the code

Add the following code to the module in webpack.base.js:

module: {
        rules: [
            // Compile es6 into ES5
            { 
                test: /\.jsx? $/./ /? That means that x has zero or one
                exclude: /node_modules/.// Do not compile files in a directory
                include: path.resolve(__dirname, '.. /src'),  // Compile the loader only in the included directory
                use: [
                    "babel-loader",]},/ / load the CSS
            {
                test: /\.css$/,
                use: ['style-loader'.'css-loader'],},]}Copy the code

Note that laoder is loaded from bottom up and from right to left, so be careful when configuring loader. Add app.css to the Styles directory and introduce it in js:

// app.css
#root {
    background-color: #f0c;
    height: 100px;
}

// index.js
import './styles/app.css'
Copy the code

Open the browser and you can see the CSS in effect:If the project is complicated, the loading speed of the project will be seriously affected. Therefore, we need another plug-in here to split the CSS code and generate a separate CSS file:

npm isntall mini-css-extract-plugin -D
Copy the code

According to the official configuration of the plug-in, we need to replace the styleloader with the loader provided by the plug-in, and configure the exported CSS file directory and file name. In order to speed up the construction of the development environment, we only split CSS in the production environment:

// webpack.prod.js
const merge = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const base = require('./webpack.base');

module.exports = merge({
    mode: 'production'.output: {
        filename: 'js/[name]_[contenthash].js'.// Entry and content hash file name, can also be hash
		chunkFilename: 'js/[name]_[contenthash].chunk.js'
    },
    module: {
        rules: [{test: /\.css$/,
                use: [  // Loader parses from bottom to top and from right to left
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            filename: '[name].css'.chunkFilename: '[name].css'.publicPath: '.. / '   //**** Replace the imported file path at the end of the package}},// 'style-loader' is not allowed when MiniCssExtractPlugin is used
                    {
                        loader: 'css-loader'.options: {
                            importLoaders: 2   // This allows the @import CSS file to be re-executed on the side of the CSS packaging loader}},]}]},plugins: [
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            filename: 'css/[name]_[hash].css'.chunkFilename: 'css/[name]_[hash].chunk.css',
          }),
    ]
}, base)
Copy the code

Since we have different CSS-loaders in dev and Prod environments, we will remove the csS-loader configuration from base and move it to dev

// webpack.dev.js
const base = require('./webpack.base');
const merge = require('webpack-merge');
const webpack = require('webpack');

module.exports = merge({
    mode: 'development'.module: {
        rules: [{test: /\.css$/,
                use: [  // Loader parses from bottom to top and from right to left
                    'style-loader'.// If the MiniCssExtractPlugin is used, styleloader cannot be used
                    {
                        loader: 'css-loader'.options: {
                            importLoaders: 2   // This allows the @import CSS file to be re-executed on the side of the CSS packaging loader}},]}]},output: {
		filename: '[name].js'.chunkFilename: '[name].js',
	}
}, base)
Copy the code

In order to support the higher es6+ syntax, we configure the.babelrc file and install the corresponding NPM package:

npm install @babel/polyfill core-js -D
Copy the code

The.babelrc file is as follows:

{
    "presets": [["@babel/preset-env".// Convert ES6 syntax to ES5
            {
                "useBuiltIns": "usage".// Compile only the code that needs to be compiled
                "corejs": "3.0.1",}],]}Copy the code

We’ll see “useBuiltIns”: “Usage” in the babelrc file. This configuration involves an advanced use of WebPack packaging, tree-shaking.

Tree-shaking: Describes removing dead-code from a JavaScript context. It relies on the static structural features of THE ES2015 module syntax, such as import and export. This term and concept was actually popularized by the ES2015 module packaging tool Rollup.

Using Tree-shaking, we can greatly reduce the size of the code, which can help improve packaging performance. For tree-shaking to work, we also need to enable it in the WebPack configuration file:

// webpack.base.js
optimization: {
        usedExports: true
    }
Copy the code

Another problem is that since tree-shaking is based on import and export, when we import CSS files, there is no export, so we need to configure tree-shaking to ignore CSS files. Add the following configuration to package.json:

// package.json
"sideEffects": [
    "*.css"."*.less"].Copy the code

In the process of packaging, each time we do a package, we create a new package file. What if we want to clean the last package before each package? We can use the clean-webpack-plugin to implement this, first install it and then configure it as follows:

// webpack.prod.js
plugins: [
        new CleanWebpackPlugin()
    ],
Copy the code

The plug-in clears packages in the dist directory by default. Next we install jquery:

npm install jquery -S
Copy the code

In index.js introduce and use:

import $ from 'jquery';
import './styles/app.css'

console.log($('#root').html('hello world'));
Copy the code

After executing NPM run build, we open it in the browser and see what jq does:However, when we look at the JS file under dist directory, we find that the jquery and business code are packaged into one page, which will lead to a very large page code when the business is complex. We further optimize, that is, js code segmentation. According to the webpack website solution, we only need to do a simple configuration, you can split the JS code:

//webpack.base.js
optimization: {
        splitChunks: {
            chunks: 'all'.// chunks: 'async', // async means that only asynchronous code is separated
            minSize: 30000.// Do code splitting when the specified size is exceeded
            MaxSize: 200000, maxSize: 200000, maxSize: 200000, maxSize: 200000
            minChunks: 1.maxAsyncRequests: 5.maxInitialRequests: 3.automaticNameDelimiter: '_'.name: true.cacheGroups: {  // Cache group: Vender if vendor's conditions are met, default if not
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10.// The higher the weight, the higher the packing priority
                    // filename: 'js/vender.js' // Package the code as a file called vender.js
                    name: 'vender'
                },
                default: {
                    minChunks: 2.priority: -20.name: 'common'.// filename: 'js/common.js',
                    reuseExistingChunk: true // Whether to reuse already packaged code}}},usedExports: true
    }
Copy the code

For the sake of space limitations, I have written out the detailed configuration of splitChunks in the comments. If you want to learn more about the configuration, you can check out the webPack website later. At this point, run NPM run build, and we can see that the code has been split:Of course, only satisfying the packaging with CSS and JS is far from enough, our project will also use a variety of images, font ICONS, CSS precompiled languages such as LESS or SCSS, because this part of the installation is relatively simple, we directly on the code. First install the dependencies:

npm install url-loader file-loader less less-loader
Copy the code

At this point our webpack.base.js module becomes:

module: {
        rules: [
            // Compile es6 into ES5
            { 
                test: /\.jsx? $/./ /? That means that x has zero or one
                exclude: /node_modules/.// Do not compile files in a directory
                include: path.resolve(__dirname, '.. /src'),  // Compile the loader only in the included directory
                use: [
                    "babel-loader",]},// Load the parsed file resources
            {
                test: /\.(jpg|png|gif)$/,
                use: {
                    loader: 'url-loader'.// Same function as file-loader, but smarter
                    options: {
                        // Configure the file name after the package. For details, see the WebPack file-Loader document
                        name: '[name].[ext]? [hash]'.outputPath: 'images/'.limit: 4096 // If the image size is larger than 4K, it will be output as a file, otherwise it will be output as base64}}},// Import fonts, SVG and other files
            {
                test: /\.(eot|ttf|svg)$/,
                use: {
                    loader: 'file-loader'}}},Copy the code

To support less, we modify the dev and prod files:

// webpack.dev.js
module: {
        rules: [{test: /\.(css|less)$/,
                use: [  // Loader parses from bottom to top and from right to left
                    'style-loader'.// If the MiniCssExtractPlugin is used, styleloader cannot be used
                    {
                        loader: 'css-loader'.options: {
                            importLoaders: 2   // This allows the @import CSS file to be re-executed on the side of the CSS packaging loader}},'less-loader',]}]}// webpack.prod.js
module: {
        rules: [{test: /\.(css|less)$/,
                use: [  // Loader parses from bottom to top and from right to left
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            filename: '[name].css'.chunkFilename: '[name].css'.publicPath: '.. / '   //**** Replace the imported file path at the end of the package}, {},loader: 'css-loader'.options: {
                            importLoaders: 2   // This allows the @import CSS file to be re-executed on the side of the CSS packaging loader}},'less-loader']]}}Copy the code

Let’s write less code to try:

body {
    #root {
        background-color: # 000;
        color: #fff; }}Copy the code

After we execute build, open it in the browser and see what it looks like:Ok, so our first and most important step has been completed. Since it is impossible to build every time the code changes during the development project, it is too time expensive. We want to see the changes in real time, so we use the devServer provided by webpack4.0. It enables our project to support hot update and hot module replacement. We need to configure it in the development environment, as follows: First, install the development server module:

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

Next, configure the dev file:

webpack.dev.js
const base = require('./webpack.base');
const merge = require('webpack-merge');
const webpack = require('webpack');

module.exports = merge({
    mode: 'development'.module: {
        rules: [{test: /\.(css|less)$/,
                use: [  // Loader parses from bottom to top and from right to left
                    'style-loader'.// If the MiniCssExtractPlugin is used, styleloader cannot be used
                    'vue-style-loader',
                    {
                        loader: 'css-loader'.options: {
                            importLoaders: 2   // This allows the @import CSS file to be re-executed on the side of the CSS packaging loader}},// 'sass-loader',
                    'less-loader'.'postcss-loader',]}]},// Server configuration
    devServer: {
        port: '8081'.// When using the HTML5 History API, any 404 response may need to be replaced with index.html
        historyApiFallback: true.// Solve the single-page routing problem,
        contentBase: '.. /dist'.open: true.// Open the browser automatically
        hot: true.// Open hot replace, CSS code with new does not refresh the page
        // hotOnly: true After hot is enabled, the browser can only be updated manually. Even if hot is true, the browser does not refresh
        proxy: {
            index: ' '.// Set index to null so that the root path can be forwarded
            'api/get': 'xxxx.com/api'.// In the first way, direct proxy to the API path
            'api/vue': {  // The second option is used when the path needs temporary replacement
                target: 'xxxx.com/api'.pathRewrite: {
                    'head': 'demo'  // In this case, the head path will be propped to demo
                },
                secure: false.// To configure HTTPS requests, false to support HTTPS
                changeOrigin: true  // Do agent distribution to allow access to other sites, break the restrictions of the site, recommended in the development environment}}},plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    output: {
		filename: '[name].js'.chunkFilename: '[name].js',
	}
}, base)
Copy the code

The above code, to use the thermal module replacement, we need to use webpack own integration plugin webpack HotModuleReplacementPlugin, in devServer, we can also set up the development environment of the proxy agent, this is by far the development of the default mode, I’ve commented on the usage and meaning of some of the properties in the code, but if you’re interested, check out the webPack documentation for more detailed configuration information. Let’s modify package.json to add the development environment instructions:

// Add to the script
"start": "webpack-dev-server --config ./build/webpack.dev.js".Copy the code

We execute NPM start, which automatically opens the browser and runs our project.

At this point, we have a basic one-page application packaging tool that supports ES6+Less/ CSS +JQuery. Of course, this is just the foundation. The multi-page applications, such as Vue/React /typescript, will be built on top of this.

Multi-page applications:

We still need to use the html-webpack-plugin we used before to develop a multi-page application, so we need to define multiple entries:

// webpack.base.js
entry: {
        main: './src/index.js'.about: './src/about.js'
    }

plugins: [
new HtmlWebpackPlugin(
        {
            template,
            title: 'WebPack but single application'.chunks: ['vender'.'main'].filename: 'index.html'}),new HtmlWebpackPlugin(
        {
            template,
            title: 'About us'.chunks: ['vender'.'about'].filename: 'about.html'}),]Copy the code

Template is the HTML path we define in public, and title is the content in the TITL tag we want to embed in the HTML template. We use this in index.html:

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <link rel="shortcut icon" href="./favicon.ico">
</head>
<body>
    <div id="root"></div>
</body>
</html>
Copy the code

In this way, the webPack-configured title can be dynamically added to the HTML page. In the new HtmlWebpackPlugin we added chunks array. This array is the JS we want to package into the page. Vender is the name defined by the Vendors in the cacheGroups attribute values we defined in Optimization, and the Vender.js file will be generated whenever the code exceeds 30000b. At this point, we run the NPM run build, after the packaging is finished, two HTML pages will be generated, the corresponding file dependencies will also be introduced, open in the browser, the test is effective ~

This completes the development of a basic multipage packaging tool, with a few optimizations:

  1. Code compression,
  2. Third party modules are lazy compilation, we can use webPack provided DLL technology to optimize
  3. Introduction of PWA technology

To compress the code, we use terser-webpack-plugin to compress JS and optimize- csS-assets -webpack-plugin to compress CSS

npm install terser-webpack-plugin optimize-css-assets-webpack-plugin -D
Copy the code

Let’s add the following configuration to webpack.prod.js:

// Import the module
/ / compress CSS
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
Js / / compression
const TerserPlugin = require('terser-webpack-plugin');

// Configure module.exports and add the following configuration
optimization: {
        minimizer: [
            new TerserPlugin({   // Compress the JS code
                cache: true.// Enable file caching
                parallel: true.// Use multiple processes to execute tasks in parallel to improve build efficiency
                sourceMap: true.// Map the error message location to the module
                terserOptions: {
                    drop_console: true.// Remove all console.log during packaging
                    drop_debugger: true  // Remove all debugger during packaging}}),new OptimizeCSSAssetsPlugin({})]  // Compress the CSS code
    },
Copy the code
  1. Pwa: Progressive Web Application (PWA) is a Web app(Web application) that provides an experience similar to that of native App. First we install dependencies:
npm install workbox-webpack-plugin --save-dev
Copy the code

Add the following script to the page where you need to do pWA:

if ('serviceWorker' in navigator) {  window.addEventListener('load'.() = > {
            navigator.serviceWorker.register('/service-worker.js').then(registration= > {
                console.log('SW registered: ', registration);
            }).catch(registrationError= > {
                console.log('SW registration failed: ', registrationError);
                });
            });
 }
Copy the code
  1. Optimize the packaging speed of DLL files

About the DLL problem, we can query the specific use method on the official website, here is not specific.

With that out of the way, let’s integrate support for react/vue/typescript with ES6+JQuery+Less/Scss.

1. Support the react

Let’s first install a Babel module:

npm install --save-dev @babel/preset-react
Copy the code

Then add the following configuration to.babelrc:

{
  "presets": [["@babel/preset-react",
      {
        "pragma": "dom".// default pragma is React.createElement
        "pragmaFrag": "DomFrag".// default is React.Fragment
        "throwIfNamespace": false // defaults to true}}]]Copy the code

Then write a react code to index.js:

import React, {Component} from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
    render() {
        return <div>react frame content.</div>
    }
}

ReactDOM.render(<App />.document.getElementById('root'));
Copy the code

Once packaged, you can see the effect in your browser.

2. Support the vue

First install the corresponding NPM package:

npm install -D vue-loader vue-template-compiler
Copy the code

Then write the following code in the WebPack configuration file:

// webpack.base.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  module: {
    rules: [
      // ... other rules
      {
        test: /\.vue$/,
        loader: 'vue-loader'}},plugins: [
    // make sure to include the plugin!
    new VueLoaderPlugin()
  ]
}
Copy the code

To parse the style in the.vue file, we need to use the vue-style-Loader module and add it to the loder after installation. Let’s try a simple vue:

<template>
  <div class="example">
      {{ msg }}
      <img src="~/images/logo.png" />
      <img :src="imgSrc" />
  </div>
</template>

<script>
import Logo from 'images/logo.png';
import 'css/common.css';

export default {
  data () {
    return {
      msg: 'Hello world! A separate '.imgSrc: Logo
    }
  }
}
</script>

<style lang="less">
.example {
  color: red;
  img {
      border: 2px solid # 000; }}</style>
Copy the code

Run dev to see the effect in your browser. It is worth noting that the relative path to the vue file will be problematic. Here we can use ~/images/logo.png or require to import the image.

3. Support the typescript

Here we use the awesome typescript-loader to compile typescript files, which is also an official recommended loader:

npm install awesome-typescript-loader --save-dev
Copy the code

Then we add the following code to module rules in the WebPack configuration file base:

{
        test: /\.tsx? $/,
        loader: 'awesome-typescript-loader'
      }
Copy the code

As a final step, add the tsconfig.json file:

{
    "compilerOptions": {
        "noImplicitAny": true."removeComments": true
    },
    "awesomeTypescriptLoaderOptions": {
        / *... * /}}Copy the code

This file has many flexible configuration options, and you can check out the typescript documentation if you want to learn more. So far, all the configuration is complete, is not very tired? Haha, of course, there are still some optimization in the scaffolding, welcome to improve together.

Incomplete optimization points:
  • Styles cannot be independently extracted from the vue file. You can only load styles by importing CSS files
  • Common CSS file problem: When multiple pages are packaged, if the same CSS is introduced, it is not possible to use this CSS file. It is hoped that the CSS file can be referenced separately as a common module.

More recommended

  • Js basic search algorithm implementation and 1.7 million data under the performance test

  • How to make front-end code 60 times faster

  • Front End Algorithm series array deweighting

  • How to control CSS sense of direction

  • Vue Advanced Advanced series – Play with Vue and vuex in typescript

  • In the first three years, talk about the top five books worth reading