This paper is mainly about multi-entry configuration, hoping to improve the development efficiency and optimize the code packaging when developing web pages without frame. If there is anything that needs to be improved in this paper, please kindly advise me.

Key words:

  1. babel7
  2. Multiple entry
  3. sass
  4. The image processing
  5. Audio and video processing
  6. Font processing
  7. gzip

Making the source code

The module overview

The directory structure is as follows:

|-build
  |-create.js
  |-utils.js
  |-webpack.base.js
  |-webpack.dev.js
  |-webpack.prod.js
|-dist
|-src
|-.babelrc
|-.eslintrc.js
|-package.json
Copy the code
// webpack.base.js
const webpack = require('webpack')
const PurgecssPlugin = require('purgecss-webpack-plugin')

const rules = require('./webpack.rules.js')
const utils = require('./utils.js')

module.exports = {
  entry: {},
  resolve: {},
  module: {},
  externals: {},
  plugins: []}// webpack.prod.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CompressionPlugin = require('compression-webpack-plugin')

const configBase = require('./webpack.base.js')
const utils = require('./utils.js')

const configProd = {
  mode: 'production'.devtool: 'none'.output: {},
  optimization: {},
  plugins: []}module.exports = merge(configBase, configProd)

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

const utils = require('./utils.js')
const configBase = require('./webpack.base.js')

const configDev = {
  mode: 'development'
  output: {},
  devServer: {},
  plugins: [].module: {}}module.exports = merge(configBase, configDev)

// webpack.rules.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
constdevMode = process.env.NODE_ENV ! = ='production'
const rules = []
module.exports = rules
Copy the code

Module. Exports and other codes are omitted.

Configuration entry

The entry tells Webapck which module to start with, packaged according to dependencies

  1. Single entry
entry: './src/index.js'
Copy the code
  1. Multiple entry
entry: {
  index: './src/index/index.js'
}
Copy the code

For multi-entry configurations, the glob library can be used to dynamically obtain entry files as follows:

// utils.js
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const glob = require('glob') // Traverse the directory
constdevMode = process.env.NODE_ENV ! = ='production'

/** * returns the absolute path to the file * @param {string} dir file path * __dirName Gets the full name of the directory where the current execution file is located (in this case, the build directory) */
function resolve(dir) {
  return path.resolve(__dirname, dir)
}

// Dynamically add entries
function getEntry(globPath) {
  var dirname, name
  return glob.sync(globPath).reduce((acc, entry) = > {
    // name ./src/pages/index/index.js
    // dirname ./src/pages/index
    // basename index.js
    dirname = path.dirname(entry)
    name = dirname.slice(dirname.lastIndexOf('/') + 1)
    acc[name] = entry
    return acc
  }, {})
}

function htmlPlugins() {}

module.exports = {
  resolve,
  getEntry,
  htmlPlugins
}

// webpack.base.js
entry: utils.getEntry('./src/pages/*/index.js'),
Copy the code

Configure the clean – webpack – the plugin

This plug-in is configured before output is configured to delete the dist directory before each package to ensure that there are no redundant files.

// webpack.prod.js
const cleanWebpackPlugin = require('clean-webpack-plugin')

plugins: [
  // Delete the dist directory
  new CleanWebpackPlugin({
    // verbose Write logs to console.
    verbose: false.// Enable output information on the console
    // dry Use boolean "true" to test/emulate delete. (will not remove files).
    // Default: false - remove files
    dry: false}),]Copy the code

Configuration export output

Customize the location and name of the output file

// webpack.dev.js
output: {
  path: utils.resolve('.. /dist'),
  / / package name
  filename: 'js/[name].js'
},

// webpack.prod.js
output: {
  path: utils.resolve('.. /dist'),
  / / package name
  filename: 'js/[name].[chunkhash:8].js'.// Block name, public block name (not entrance)
  chunkFilename: 'js/[name].[chunkhash:8].js'.// Package the generated index.html file to reference the resource prefix
  // Also prefixes the URL published to online resources
  // Use relative path, default is ""
  publicPath: '. '
},
Copy the code

hash

File names are hash to make better use of the browser’s cache of static files.

  1. hash

Every build produces a new hash, even if the contents of the file have not changed, which is obviously not what we want. Can be used in development environments, but is not recommended for production environments.

  1. chunkhash

Each entry has a hash value that changes when the contents of a file change in the entry dependency. Suitable for production environment.

  1. contenthash

The hash value is calculated based on the contents of the package. The hash value remains the same as long as the contents of the package remain unchanged. Suitable for production environment.

There are also related articles on the difference between the three. For example, I found the difference between Hash, Chunkhash, and Contenthash in Webpack for reference.

Configuration mode mode

None, development, and production default to production

// webpack.prod.js
mode: 'production'

// webpack.dev.js
mode: 'development'
Copy the code

Webpack4 uses built-in optimization policies for different modes, which can reduce a lot of configuration. Refer to webpack mode

Configure resolution policy Resolve

// webpack.base.js
resolve: {
  // import alias during import to reduce time-consuming recursive parsing operations
  alias: {
    The '@': resolve('.. /src'),
    'assets': utils.resolve('.. /src/assets')},extensions: [
    '.js'.'.json']}Copy the code

Configure the rule Module for parsing and converting files

Configure rules for different file types in the project

// webpack.base.js
module: {
  // Ignoring large libraries can improve build performance
  noParse: /jquery|lodash/.rules: []}Copy the code
  1. Js parsing rules
// webpack.rules.js
rules: [
  {
    test: /\.js$/.use: ['babel-loader'].// Do not check the js files under node_modules
    exclude: '/node_modules/'}]// .babelrc
{
	"presets": [["@babel/preset-env",
      {
        "useBuiltIns": "usage"."corejs": 3."modules": false}}]]// pages/index/index.js
import 'core-js/stable'
import 'regenerator-runtime/runtime'
Copy the code

According to the configuration of the official website Usage Guide, core-js@3 is used here to realize polyfill. Since babel7 has been deprecated @babel/ Polyfill and core-js@2, it will not be updated. New features will only be added to core-js@3. To avoid further changes, use 3 directly. It’s just that the bag is a little bit too big, so it balances itself, and if you don’t like it, just use @babel/ Polyfill.

There is an article about this core-js@3 very clear, can refer to.

  1. Sass parsing rules
// webpack.rules.js
rules: [
  {
    test: /\.s[ac]ss$/i.use: [
      devMode
        ? 'style-loader'
        : {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // you can specify a publicPath here
              // by default it use publicPath in webpackOptions.output
              publicPath: '.. / '}},'css-loader'.'postcss-loader'.'sass-loader']}]// webpack.prod.js
plugins: [
  new MiniCssExtractPlugin({
    filename: 'css/[name].[contenthash:8].css'.chunkFilename: 'css/[name].[contenthash:8].css'}),]Copy the code
  1. html
// webpack.base.js
plugins: [...utils.htmlPlugins('./src/pages/*/index.html')]

// utils.js
function htmlPlugins(globPath) {
  var dirname, name
  return glob.sync(globPath).reduce((acc, entry) = > {
    dirname = path.dirname(entry)
    name = dirname.slice(dirname.lastIndexOf('/') + 1)
    acc.push(new htmlWebpackPlugin(htmlConfig(name, name)))
    return acc
  }, [])
}

function htmlConfig(name, chunks) {
  return {
    template: `./src/pages/${name}/index.html`.filename: `${name}.html`.// favicon: './favicon.ico',
    // title: title,
    inject: true.chunks: [chunks],
    minify: devMode
      ? false
      : {
          removeComments: true.collapseWhitespace: true}}}Copy the code
  1. The picture
// webpack.rules.js
rules: [
  {
    test: /\.(png|jpe? g|gif)(\? . *)? $/.use: [{loader: 'url-loader'.options: {
          esModule: false.limit: 4 * 1024.name: 'img/[name].[hash:8].[ext]'}}, {loader: 'img-loader'.options: {
          plugins: [
            require('imagemin-pngquant') ({speed: 2 / / 1-11
            }),
            require('imagemin-mozjpeg') ({quality: 80 / / 1-100
            }),
            require('imagemin-gifsicle') ({optimizationLevel: 1 / / 1, 2, 3}}}]}, {test: /\.(svg)(\? . *)? $/.use: [{loader: 'url-loader'.options: {
          name: 'img/[name].[hash:8].[ext]'}}]},]Copy the code

Usage:

background: url(~assets/index/icons/ic-star-16px.png);
Copy the code
import wukong from 'assets/index/wukong.jpg'
Copy the code
<img src="~assets/index/wukong.jpg" alt="wukong" />
Copy the code

There are a few points to note here:

  • If limit is configured for url-loader and file-loader, images smaller than the limit value are converted to Base64 by url-loader, and images larger than the limit value are directly processed by file-loader. So file-loader should be installed even though it does not appear in the rule.
  • Html-loader is used to process HTML files. In this case, it mainly processes images in HTML files. The image is processed by urL-loader. After processing the image, set the correct path or base64 to SRC. To use webpack alias configuration in HTML, you need to add ~ in front of it, and then urL-loader to configureesModule: falseYou can’t go wrong.
  1. Audio and video and fonts
// webpack.rules.js
rules: [
  {
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/.loader: 'url-loader'.options: {
      limit: 4 * 1024.name: '[name].[hash:8].[ext]'.outputPath: 'media'}}, {test: /\.(woff2? |eot|ttf|otf)(\? . *)? $/.loader: 'url-loader'.options: {
      limit: 4 * 1024.name: '[name].[hash:8].[ext]'.outputPath: 'font'}}]Copy the code

devserver

// webpack.dev.js
devServer: {
  contentBase: utils.resolve('.. /src'), // Tell the server from which directory to serve the content
  publicPath: '/'.// The package file in this path is accessible in the browser
  port: '8090'.overlay: true.// An error appears on the browser page
  open: true.// Automatically open the browser
  //stats: "errors-only", //stats: "errors-only"
  historyApiFallback: false.// 404 will be replaced with index.html
  inline: true.// Inline mode, real-time refresh
  hot: true.// Enable hot update
  proxy: {
    '/api': {
      target: 'https://example.com/'.changeOrigin: true.pathRewrite: {}}}},plugins: [
  / / hot update
  new webpack.HotModuleReplacementPlugin()
],
Copy the code
  1. ContentBase + output.publicPath + output.filename. This path can only be accessed by the browser to access the bundle in memory
  2. There are two uses for publicPath:
  • Webpack-dev-server is used as the output directory in memory.
  • Read by other Loader plug-ins and modify the URL.

devtool

// webpack.dev.js
devtool: 'cheap-eval-source-map'.// webpack.prod.js
devtool: 'none'.Copy the code

This option controls whether and how the Source map is generated. The website explains and compares the different options in more detail.

optimization

// webpack.prod.js
optimization: {
  runtimeChunk: {
    name: 'manifest'
  },
  splitChunks: {
    cacheGroups: {
      vendors: {
        name: 'vendors'.test: /[\\\/]node_modules[\\\/]/.priority: - 10.chunks: 'initial' // Only import files are processed
      },
      vendors: {
        name: 'chunk-common'.minChunks: 2.priority: - 20.chunks: 'initial'.reuseExistingChunk: true}}}},Copy the code

RuntimeChunk and splitChunks are primarily optimized for browser caching, and can be omitted if not.

externals

// webpack.base.js
externals: {
  'jquery': 'window.jquery'
},
Copy the code

Purpose: To prevent packages from being packaged into bundles and to obtain these extension dependencies externally at runtime. $(‘#id’); $(‘#id’); $(‘#id’); However, if you use modular JQ plugins locally, add this externals configuration. Here’s why:

; (function(window, factory) {
  if (typeof exports === 'object') {
    module.exports = factory(require('jQuery'))}else if (typeof define === 'function' && define.amd) {
    define(['jQuery'], factory)
  } else {
    factory()
  }
})(window.function($) {
  $.fn.green = function() {$(this).each(function() {$(this).css('color'.'green')})}})Copy the code

The code above is a simple JQ plug-in that uses the UMD modularity scheme. If (typeof exports === ‘object’) will be resolved by Webpack to if (true). If (typeof exports === ‘object’) will be resolved by Webpack to require(‘jquery’). Therefore, an error will be reported and packaging cannot succeed.

ProvidePlugin

plugins: [
  // Automatically load modules without import or require
  new webpack.ProvidePlugin({
    $: 'jquery'.jQuery: 'jquery'.'window.jQuery': 'jquery'}),]Copy the code