Historical issues

  • ES Modules has an environment compatibility problem
  • Too many module files and frequent network requests
  • All front-end resources need to be modular

There is no doubt that modularity is necessary

Put forward the idea

The target

  • New feature code compiled
  • modularjavascriptpackaging
  • Supports different types of resource modules

Packaging tools

The packaging tool addresses the overall modularity of the front end, not just thejavascriptmodular

  • WebpackModule Bundler
    • According to the module Loader – code with environmental compatibility issues can be compiled and converted by the Loader during packaging

    • Code Splitting — It’s a way to package all the Code in an app in a way that we need to do, which means we don’t have to package all the Code together and have huge file problems

    • Asset Module – what problem type of resource files should be loaded in a modular manner

Webpack

1. Webpack is a quick start

Note the use of webpack ^4.40.2, webpack-cli ^3.3.9

  • yarn add webpack webpack-cli
  • package.jsonadd"build": "webpack"

2. Webpack configuration file

const path = require('path')
module.exports = {
    entry: './src/index.js'.output: {
        filename: 'bundle.js'.path: path.join(__dirname, 'output')}}Copy the code

3. Working mode

yarn webpack --mode none

  • development
  • production
  • None – Packaging of the original state
  • Webpack.js.org/configurati…

4. Operation principle of Webpack packing results

5. Load the resource module

  • LoaderWebpackBy virtue of the core featuresLoaderYou can load any type of resource

  • configurationLoader
// webpack.config.js

module: {
    // Configure the loading rules for other resource modules
    rules: [{test: /\.css$/,
        use: [
          'style-loader'.'css-loader'
        ] // When it is an array, the last one is executed first}}]Copy the code

6. Import the resource module

  • JavaScriptDrive the business of the entire front-end application

// In main.js
import './main.css'
Copy the code
  • WebpackIt is recommended that we import resources dynamically according to the needs of the code

7. File resource loader

  • file-loader

Configure the root directory of the website

The principle of

8. URL loader

  • Data URLsIs a kind of presenturlI can directly represent the contents of the fileurlThe text already contains the contents of the file, so we’re using thisurlI will not send anyhttprequest

  • loaderurl-loader
    • Small file usageData URLsTo reduce the number of requests
    • Large files are separately extracted and stored to improve loading speed
{
    test:/\.png$/,
    use: {
        loader: 'url-laoder'.options: {
            limit: 10 * 1024 // only files smaller than 10KB will be converted}}}Copy the code

9. Common loader

  • Compiler conversion class
    • eg: css-loader
  • File manipulation class
    • eg: file-loader
  • Code inspection class
    • eg: eslint-loader

10. Webpack with ES 2015

  • Install Babel correlationyarn add babel-=loader @babel/core @babel/preset-env -D
// rules
{
    test: /\.js$/,
    use: {
        loader: 'babel-loader'.options: [
            presets: ["@babel/preset-env"]]}}Copy the code

Webpack simply packages the utility loader that can be used to compile the transformation code

11. How Webpack loads resources

  • followES ModulesThe standardimportThe statement
  • followCommonJSThe standardrequirefunction
  • followAMDThe standarddefineFunctions andrequirefunction
  • In the style code@importInstructions andurlfunction
  • HTMLCode in the image tagsrcattribute

Pay attention to

  • The SRC attribute in HTML triggers packaging, and if you want to trigger other attribute reference files, you need to do the following
// rules
{
    test: /\.html$/,
    use: {
        loader: 'html-loader'.options: {
            attrs: ['img:src'.'a:href']}}}Copy the code

12. Core working principle of Webpack

  • LoaderMechanism isWebpackThe core of the

13. Develop a Loader

// my-markdown-loader.js
const marked = require('marked')
module.exports = source= > {
    const html = marked(source)
    // Either of the following is ok
    // A
    return `module.exports = The ${JSON.stringify(html)}`
    return `module.exports = The ${JSON.stringify(html)}`
    
    // B An html-loader is required to continue processing the result if the following case is used
    return html
}
Copy the code
// B
// rules
{
    test: /\.md$/,
    use: [
        'html-loader'.'./my-markdown-loader']}Copy the code

14. Plugin mechanism

Enhance Webpack automation capabilities

LoaderFocus on resource module loading

PluginSolve other automation tasks

  • Eg: removedistdirectory
  • Eg: Copy static files to the output directory
  • Eg: Compress the output code

14.2 Auto Clear Output Directory Plug-in

  • useclean-webpack-pluginThe plug-in
yarn add clean-webpack-plugin -D
Copy the code

Use:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

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

14.3 Automatically Generating HTML Plug-ins

  • html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
    HtmlWebpackPlugin can be configured with parameters
    // https://www.npmjs.com/package/html-webpack-plugin
    new HtmlWebpackPlugin({
        title: 'webpacl plugin sample'.meta: {
            viewport: 'width-device-width'
        },
        template: './src/index.html'})]// Automatically generates HTML to dist

Copy the code
  • The best way to do this is to configure templates in source code
<! -- HtmlWebpackPlugin to configure template path -->
<h1><%= htmlWebpackPlugin.options.title %></h1>
Copy the code
  • Output multiple page files simultaneously
plugins: [
    new HtmlWebpackPlugin({
        title: 'webpacl plugin sample'.meta: {
            viewport: 'width-device-width'
        },
        template: './src/index.html'
    }),
    // Used to generate about.html
    new HtmlWebpackPlugin({
        filename: 'about.html'})]Copy the code

14.4 Static Files are copied

  • copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins: [
    new CopyWebpackPlugin([
        'public']]Copy the code

15. Develop a plug-in

  • PluginThrough the hook mechanismwww.webpackjs.com/api/compile…
  • A function or a containapplyMethod object
class MyPlugin {
  apply(compiler) {
    // Find the emit hook (before generating resources to the output directory)
    compiler.hooks.emit.tap('MyPlugin'.compilation= > {
      // compilation => can be understood as the context of this compilation
      for (let name in compilation.assets) {
        const contents = compilation.assets[name].source()
        const withoutComments = contents.replace(/\/\*\*+\*\//g.' ')
        compilation.assets[name] = {
          source: () = > withoutComments,
          size: () = > withoutComments.length
        }

      }
    })
  }
}
Copy the code

WebpackHandling development scenarios

Vision: Ideal development environment

  • Idea 1. ToHTTP Serverrun
  • Idea 2. Automatic compilation + automatic refresh
  • Idea 3. OffersSource Mapsupport

1. Automatic compilation

  • watchWorking mode, listening for file changes, automatic repackaging
    • yarn webpack --watch

2. Automatically refresh the browser

  • You want the browser to refresh automatically after compiling
    • BrowserSync

It’s difficult to operate, and it’s inefficient

3. Webpack Dev Server

  • Provides for developmentHTTP Server
    • integrationAutomatic compilationAutomatically refresh the browserThe function such as
yarn add webpack-dev-server -D

The result of packing is not written to disk and stored in memory
yarn webpack-dev-server
Copy the code

3.2 Dev Server by default onlyservePackage output file

  • As long as it isWebpackOutput files can be accessed directly if other static resources are needed as you sayserve
// webpack.config.js
{
    devServer: {
        // Specify an additional lookup resource directory for the development server
        contentBase: './public'}}Copy the code

3.3 Dev Server agent API

  • http://localhost:8080/index.html
  • www.example.com/api/users
  • This forms a cross-domain request

You do not need to enable same-origin deploymentCORS, cross-domain is not allowed

Problem: Cross-domain interface issues during development

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        // http://localhost: 8080/api/users -> https://api.github.com/api/users
        target: 'https://api.github.com'.// http://localhost: 8080/api/users -> https://api.github.com/users
          pathRewrite: {
            '^/api': ' '
          },
        // Localhost: 8080 cannot be used as the host name for requesting Github
        changeOrigin: true}}}}Copy the code

4. Source Map

Running code is completely different from source code. If you need to debug your application, error messages cannot be located. Debugging and error reporting are based on running code

  • For example,Jquery - 3.4.1 track. Min. Js
  • If you want to useSource MapNeed to be added at the end of the file
/ / # sourceMappingURL - jquery - 3.4.1 track. Min. The mapCopy the code

4.2 Configuring Source Map on Webpac

  • WebpackSource MapThere are many types of support, each with varying efficiency and effectiveness
// webpack.config.js
module.exports = {
  devtool: 'source-map',}Copy the code

Support table (also refer to the official websiteWebpack.js.org/configurati… )

The eval mode

eval('console.log(123) //# sourceURL=./foo/bar.js')
// means that we can change the code we execute through eval via sourceURL
Copy the code

Devtool mode comparison

  • Eval – Whether to useevalExecute module code
  • Cheap – Source Map contains row information
  • Module – Whether it can be obtainedLoaderProcess the previous source code
  • How to Choose (Suggestions)
    • Development mode – cheap-module-eval-source-map
      • Each line of code does not exceed 80 characters
      • The code throughLoaderAfter the conversion, the difference is large
      • It doesn’t matter if you pack slowly the first time, but repack relatively quickly
    • Production mode – None
      • Source MapIt exposes the source code
      • Debugging is a development phase thing
      • If not confident in the code – suggestionnosources-source-map

Understand the differences between different modes and adapt to different environments and scenarios

const HtmlWebpackPlugin = require('html-webpack-plugin')

const allModes = [
  'eval'.'cheap-eval-source-map'.'cheap-module-eval-source-map'.'eval-source-map'.'cheap-source-map'.'cheap-module-source-map'.'inline-cheap-source-map'.'inline-cheap-module-source-map'.'source-map'.'inline-source-map'.'hidden-source-map'.'nosources-source-map'
]

module.exports = allModes.map(item= > {
  return {
    devtool: item,
    mode: 'none'.entry: './src/main.js'.output: {
      filename: `js/${item}.js`
    },
    module: {
      rules: [{test: /\.js$/,
          use: {
            loader: 'babel-loader'.options: {
              presets: ['@babel/preset-env']}}}]},plugins: [
      new HtmlWebpackPlugin({
        filename: `${item}.html`})]}})Copy the code

5. Auto-refresh issues

The module can also be updated in time without refreshing the page

5.2 HMR – Hot Module Replacement

  • Hot plug – To plug and remove devices on a running machine at any time
  • WebpackHot replacement refers to replacing a module in real time during application running without affecting application running status
  • HMRWebpackOne of the most powerful features – greatly improve the efficiency of developers

5.3 open the HMR

  • HMRIntegrated into thewebpack-dev-server
    • runwebpack-dev-server --hot
    • It can also be enabled through a configuration file
    const webpack = require('webpack')
    
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
    // Run directly
    // yarn webpack-dev-server
    Copy the code
  • WebpackIn theHMRIt does not work out of the box
  • The module hot replacement logic needs to be manually processed

Why do hot updates to style files work out of the box?

Because there are style filesLoaderProcessing instyle-loaderHot updates to style files are already handled

The style file is simple, and you just need to replace it with the browser

Projects created through scaffolding are internally integratedHMRplan

Conclusion: We need to handle it manuallyJSHot replacement after module update

5.4 HMR APIs

Manually handle hot replacement after module update

  • To deal withJSModule hot replacement and processingThe pictureModule hot replacement
// main.js  
import createEditor from './editor'
import background from './better.png'
import './global.css'

const editor = createEditor()
document.body.appendChild(editor)

const img = new Image()
img.src = background
document.body.appendChild(img)

// ============ the following is used to handle HMR, independent of business code ============

// console.log(createEditor)

if (module.hot) {
  let lastEditor = editor
  module.hot.accept('./editor'.() = > {
    // console.log(' Editor module updated, need to handle hot replacement logic manually here ')
    // console.log(createEditor)

    const value = lastEditor.innerHTML
    document.body.removeChild(lastEditor)
    const newEditor = createEditor()
    newEditor.innerHTML = value
    document.body.appendChild(newEditor)
    lastEditor = newEditor
  })

  module.hot.accept('./better.png'.() = > {
    img.src = background
    console.log(background)
  })
}
Copy the code

5.5 HMR Precautions

  • To deal withHMRCode error causes an automatic refresh
  • Not enabledHMRIn the case of,HMR APIAn error
    • In devServer: {hot: true, hotOnly: true} if these two are not enabled, module.hot does not exist

WebpackProduction environment optimization

  • Development environments focus on development efficiency
  • Production environments focus on operational efficiency

Create different configurations for different work environments

1. Configurations in different environments

  • Configuration files export different configurations based on different environments
  • Each environment corresponds to a configuration file

1.2 Webpack configuration files support exporting a function

module.exports = (env, argv) = > {
    if (env == 'production') {
        // ...}}Copy the code

2. Use configuration files in different environments

  • webpack.common.js
  • webpack.dev.js
  • webpack.prod.js

This parameter must be configured withcommonInside the information to do merge, the community provides a better merge toolwebpack-merge

yarn webpack --config webpack.prod.js
Copy the code

3. Optimize the configuration

3.1 DefinePlugin

Inject global members into your code

  • In the Production environment, one is started and injected by defaultprocess.env.NODE_ENVconstant
const webpack = require('webpack')
module.exports = {
  mode: 'none'.entry: './src/main.js'.output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({
      // The value requires a snippet of code
      // if it is 'https://api.example.com'
      // Console. log(API_BASE_URL) ==> console.log(https://api.example.com)
      API_BASE_URL: JSON.stringify('https://api.example.com')]}})Copy the code

3.2 Tree Snaking

“Shake off” unreferenced parts of code (unreferenced code-dead-code)

  • It is automatically enabled in production mode

  • Tree Shaking is not about configuration options, it’s about optimizing a set of functions together

  • Optimization configures optimization functionality within Webpack

    • Webpack.docschina.org/configurati…
    {
      optimization: {
        // The module exports only used members
        usedExports: true.// Compress the output
        // minimize: true}}// Use usedExports: true and take care: true is similar to Tree Shaking
    // usedExports is responsible for marking "dead leaves"
    // Take care to shake them off
    Copy the code
    • Merging module functionsScope Hoisting
      • As much as possible, combine all modules and output them into a single function – increasing operating efficiency and reducing the volume of code (this feature is also known as’ Scope promotion ‘).
    optimization: {
        // The module exports only used members
        usedExports: true.// Merge each module into a function if possible
        concatenateModules: true,}Copy the code

Tree Shaking and Babel

Outlook: A lot of data shows that Shaking doesn’t work when you use Babel Tree. Here’s how it works.

  • Tree ShakingPrerequisite for useES Modules
    • byWebpackPackaged code must be usedESM
    • WebpackThe modules are assigned to different modules according to their configuration before packaging themLoaderProcess, and finally package the results together
      • So in order to convert the codeESMAScriptFeatures that we usebabel-loaderTo deal with
      • whilebabel-loaderWhen working with code, it is possible to changeES ModulesConverted toCommonJS
  • To explore the process
module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [{test: /\.js$/,
        use: {
          loader: 'babel-loader'.options: {
            presets: [
              // If Babel has already transformed ESM when loading the module, Tree Shaking will fail
              // ['@babel/preset-env', { modules: 'commonjs' }]
              // ['@babel/preset-env', { modules: false }]
              // You can also use the default configuration, i.e. Auto, so that babel-Loader automatically shuts down ESM conversion
              ['@babel/preset-env', { modules: 'auto'}]]}}}]},optimization: {
    // The module exports only used members
    usedExports: true.// Merge each module into a function if possible
    // concatenateModules: true,
    // Compress the output
    // minimize: true}}Copy the code
    1. babel-loaderThere is clear supportESModules

    1. Find the ones we usepreset-env

4. There are sideEffects

SideEffects has nothing to do with Tree Shaking

  • Side effect: What the module does when it executes other than exporting members
  • sideEffectsCommonly used innpmPackage flags whether there are side effects
// webpack.config.js

module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'
  },
  optimization: {
    sideEffects: true
    // When enabled, Webpack will check whether the current code belongs to the package.json
    // sideEffects to determine if this has sideEffects,
    // If there are no side effects, the unused modules will not be packaged}}// package.json
{
  "sideEffects": false  // All the code in the project affected by our current package.json has no side effects - without side effects, it will be removed
}
Copy the code

4.2 sideEffects note

Make sure your code really has no side effects

  • For example,
// Add an extension method to the prototype for Number
// extend.js
Number.prototype.pad = function(size) {
  // Convert numbers to strings => '8'
  let result = this + ' '
  // Add a 0 before a number => '008'
  while(result.length < size) {
      result = '0' + result
  }
  return result
}
// This is the side effect code

// In main.js
import 'extend.js'

console.log((8).pad(3))

Copy the code

If used like this: set no side effects in the Side effects configuration, the code in exten.js will not be packaged

For example, the global CSS module is a side effect, if set to no side effect, packaging will ignore it

So, at configuration time, you can tell WebPack which side effects are available

// package.json
{
  // "sideEffects": false
  "sideEffects": [
    "./src/extend.js"."./src/global.css"]}Copy the code

5. Code Splitting

Background: Because all the code ends up packaged together, the bundle is too bulky

  • Not every module is necessary at startup
  • Subcontract, load on demand

The current mainstream version of HTTP1.1 is inherently flawed – eg: same-domain parallel request restriction

Do not package problem, resource file too much:

  • Each request has a certain amount of delay
  • The request ofHeaderWasted bandwidth traffic

So module packaging is definitely necessary

Webpack implements subcontracting in two main ways
  • Multiple entry packing
  • Dynamic import

5.2 Multi-Entry Packaging

  • Multi-page applications – one page for each package entry
// webpack.config.js
entry: {
  index: "./src/index.js".album: "./src/album.js"
},
output: {
    filename: "[name].bundle.js"
},
plugins: [
  new HtmlWebpackPlugin({
    title: 'Multi Entry'.template: './src/index.html'.filename: 'index.html'.// Configure the bundle referenced by the output HTML, using chunks
    chunks: ['index']}),new HtmlWebpackPlugin({
    title: 'Multi Entry'.template: './src/album.html'.filename: 'album.html'.chunks: ['album'"})"Copy the code

5.3 Extracting common Modules

{
  optimization: {
    splitChunks: {
      chunks: 'all'}}}Copy the code

5.4 Dynamic Import

  • Load on Demand – Load a module when it is needed
  • Dynamically imported modules are automatically subcontracted
// import posts from './posts/posts'
// import album from './album/album'

const render = () = > {
  const hash = window.location.hash || '#posts'

  const mainElement = document.querySelector('.main')

  mainElement.innerHTML = ' '

  if (hash === '#posts') {
    // mainElement.appendChild(posts())
    import(/* webpackChunkName: 'posts' */'./posts/posts').then(({ default: posts }) = > {
      mainElement.appendChild(posts())
    })
  } else if (hash === '#album') {
    // mainElement.appendChild(album())
    import(/* webpackChunkName: 'album' */'./album/album').then(({ default: album }) = > {
      mainElement.appendChild(album())
    })
  }
}

render()

window.addEventListener('hashchange', render)

Copy the code

5.5 Magic Comments

  • /* webpackChunkName: '(name)' */
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) = > {
  mainElement.appendChild(posts())
})
Copy the code

6. MiniCssExtractPlugin

Extract CSS into a single file and load CSS modules on demand through this plug-in

  • If the style size is not very large, taking a percentage of a single file can be counterproductive
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
    rules: [{test: /\.css$/,
        use: [
          // 'style-loader', // inject the style through the style tag
          MiniCssExtractPlugin.loader,
          'css-loader']]}},plugins: [
  new MiniCssExtractPlugin()
]
Copy the code

7. OptimizeCssAssetsWebpackPlugin

Compress the output CSS file

  • WebpackThe built-in compression is aimed atjsThe file is
  • forCSSuseoptimize-css-assets-webpack-plugin
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
plugins: [
    new OptimizeCssAssetsWebpackPlugin()
  ]
Copy the code

Note:WebpackIt is recommended that plug-ins such as this compression class be configured in arrays for unified control by the Miner option

optimization: {
    minimizer: [ // WebPack assumes that as long as this array is configured, it will assume that we need to customize the configuration of the compression plug-in
        new OptimizeCssAssetsWebpackPlugin() // Configure this normally compressed js file is not compressed again]}// The built-in JS plug-in needs to be set back
// yarn add terser-webpack-plugin -D

const TerserWebpackPlugin = require('terser-webpack-plugin')
optimization: {
    minimizer: [
        new OptimizeCssAssetsWebpackPlugin(),
        new TerserWebpackPlugin()
    ]
}

Copy the code

8. Output file name Hash (substitutions)

In production mode, the file name is Hash

  • Usage:filename: '[name].[hash].bundle.js'
  • hash: One is generated for each buildhash. It is related to the whole project and changes as soon as the project file changeshash.
  • chunkhashAnd:webpackpackage-generatedchunkRelated. eachentry, there will be differenthash.
  • contenthash: is related to the contents of a single file. Changes when the contents of the specified file are changedhash.

Hash can specify length ‘[contenthash:8]’

For cache control, CONTenthash :8 is the best choice