Part2-2 Modular development and specification standards

First, the process of modular evolution

  • Stage1 File partition mode

    Store each function and related data in a separate file, with the convention that each file is a separate module, imported via the Script tag

    <script src="module-a.js"></script>
    <script src="module-b.js"></script>Var name = 'module-a' function method1 () {console.log(name + '#method1')}<script>
    // Name conflict
    method1()
    // Module members can be modified
    name = 'foo'
    </script>
    Copy the code

    Disadvantages:

    1. Global scope of pollution.

    2. Naming conflicts.

    3. Module dependencies cannot be managed

  • Stage2 Namespace mode

    All module members are mounted on an object

    module-a.js
    // module A Related status data and function functions
    var moduleA = {
      nameA: ' '.m1: function () {}}module-b.js
    var moduleB = {
      nameB: ' '.m2 : function () {}}Copy the code

    Disadvantages:

    1. Module members can still be modified.

    2. Module dependencies cannot be managed.

  • Stage3 IIFE: Anonymous function self-invocation (closure)

    Provide private space for modules by placing each member of a module in a private scope provided by a function.

    // module A Related status data and function functions; (function () {
      var name = 'module-a'
      
      function method1 () {
        console.log(name + '#method1')}function method2 () {
        console.log(name + '#method2')}// Expose the member to the global object
      window.moduleA = {
        method1: method1,
        method2: method2
      }
    })()
    Copy the code

    Resolved the issue of private scopes where modules cannot modify members between them.

Second, modular specification

1.CommonJS specification: NodeJS specification

  • A file is a module.
  • Each module has a separate scope.
  • Exports members through module.exports.
  • Load the module via the require function.

CommonJS loads modules synchronously, which works fine in Node but not in the browser because of asynchronous tasks.

2. Asynchronous Module Definition (AMD) : defines Asynchronous modules

Require.js, which is also a very powerful module loader.

  • Each module must be defined by the define function.
  • The define function accepts two parameters by default
// Because jQuery is defined in an AMD module named jQuery
// You must use the name 'jquery' to get the module
Jquery.js is not in the same directory, so you need to specify a path
// Parameter 1: module name, module 2: dependency, module 3: corresponds to parameter 2
define('module1'['jquery'.'./module2'].function ($, module2) {
  return {
    start: function () {$('body').animate({ margin: '200px' })
      module2()
    }
  }
})
// Load a module
require(['./module1'].function () {
  module1.start()
})
Copy the code

Disadvantages:

  1. Relatively complex to use.
  2. Module JS file requests are frequent.

3.ES Modules

Best implementation of modularity: ES Modules in a browser environment, Common JS in a Node environment

3.1 Features of ES Modules

  1. Strict mode is used automatically in ESM, this is undefined in global mode and ‘use strict’ is ignored

    <script type="module">
      console.log(this); // undefined
    </script>
    <script>
      console.log(this); // window
    </script>
    Copy the code
  2. Each ES Module is a separate scope

    <script type="module">
      const a = 1
    console.log(a); / / 1
    </script>
    <script type="module">
      console.log(a); / / an error
    </script>
    Copy the code
  3. ESM requests external JS modules through CORS

    <! -- Normal access -->
    <script type="module" src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
    <! - Access to the script at the from 'https://libs.baidu.com/jquery/2.0.0/jquery.min.js' origin' http://127.0.0.1:5500 'has had blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. -->
    <script type="module" src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    Copy the code
  4. The ESM script tag delays script execution

    <! -- Pop the box first, then print -->
    <script src="./01.js"></script>  
    <p>Hahaha hahaha</p> 
    <! Print first and then pop the box, which is equivalent to adding the defer attribute -->
    <script type="module" src="./01.js"></script> 
    <p>Hahaha hahaha</p>
    Copy the code

3.2 The core functionality of ES Modules

  1. export

    <script type="module" src="./app.js"></script>
    Copy the code
    // 01.js
    // export const name = 'zs'
    
    // export function start () {
    // console.log('start');
    // }
    
    const name = 'zs'
    
    function start () {
      console.log('start');
    }
    export { name, start } // export1
    // If you use as to export member variables, import the corresponding name1, start1
    export { name as name1, start as start1 } // export2
    // Export default
    export { name as default, start as start1 } // export3
    // export default name // export4
    Copy the code
  2. The import

    // app.js
    import { name, start } from './01.js' // import1
    // Import the corresponding renamed member variables
    import { name1, start1 } from './01.js' // import2
    console.log(name1);
    start1()
    // Import default to be renamed import3
    import { default as name1, start1 } from './01.js' / / write one
    import name1, { start1 } from './01.js' / / write two
    // Corresponding to as export
    console.log(name1);
    start1()
    // import name from './01.js' // import4
    Copy the code
  3. Matters needing attention

    • The way to export and import is basic syntax.

      Export {name, start}
      Import {name, start} from './01.js'
      Copy the code
    • Exporting is a memory space, not a copy of a value.

      let name = 'zs'
      let age = 18
      export { name, age }
      setTimeout(() = > {
        name = 'ls'
      }, 1000)
      
      import { name, age } from './01.js'
      // Corresponding to as export
      console.log(name, age); // zs 18
      
      setTimeout(() = > {
        console.log(name, age); // ls 18
      }, 1500)
      Copy the code
    • The imported member is a read-only member.

      import { name, age } from './01.js'
      name = 'wangwu' // app.js:6 Uncaught TypeError: Assignment to constant variable. An error
      // Corresponding to as export
      console.log(name, age); // zs 18
      
      setTimeout(() = > {
        console.log(name, age); // ls 18
      }, 1500)
      Copy the code
  4. Usage of imports

    // 1. Do not omit the.js suffix
    / / import {name, age} from '/' 01 / / app, js: 1 GET http://127.0.0.1:5500/part2-2/01 net: : ERR_ABORTED 404 (Not Found)
    import { name, age } from './01.js'
    
    // 2.index.js cannot be omitted
    // import { name, age } from './utils'
    import { name, age } from './utils/index.js'
    
    / / 3.. / Cannot be omitted
    // import { name, age } from 'utils'
    import { name, age } from './utils/index.js'
    
    // 4. You can use the full path or URL
    import { name, age } from 'http://127.0.0.1:5500/utils/index.js'
    
    // 5. Import the module directly
    import './utils/index.js'
    
    // 6. Use * to export all module members
    import * as obj from './01.js' // Module {Symbol(Symbol.toStringTag): "Module"}
    
    // dynamic import
    import('./01.js').then(res= > {
      console.log(res);
      // Module {Symbol(Symbol.toStringTag): "Module"}
    })
    Copy the code
  5. ES Modules interact with CommonJS in node

    • You can import CommonJS Modules in ES Modules
    • You can’t import ES Modules in CommonJS
    • The CommonJS module always exports only one default member
    • Note that import is not a structured export object, but a fixed syntax

Common modular packaging tools

1. webpack

Webpack.config.js configuration file

entry

Specifies the path to the Webpack package file.

module.exports = {
  // The./ cannot be omitted when using paths
  entry: './src/main.js' 
}
Copy the code
output

Sets the output file location.

  • Filename: specifies the output filename
  • Path: Specifies the complete absolute path to the output file directory
const path = require('path')
module.exports = {
  ...
  // Output file
  output: {
    // Output the file name
    filename: 'bundle.js'.// Output file path, complete absolute path
    path: path.join(__dirname, 'dist'),}}Copy the code
mode

Set up the packaging environment, default to Production

  • None does not use any default optimization options
  • Production uses the production configuration optimization option
  • Development uses development configuration optimization options
module.exports = {
  // Package the environment
  mode: 'none',}Copy the code
loader

File loader, working principle: responsible for resource files from input to output conversion

css-loader

The style file loader needs to be used with style-CSS

module.exports = {
  ...
  module: {
    rules: [{test: /.css$/.// Run from back to front
        use: [
          // Mount the CSS to the style tag
          'style-loader'.'css-loader'}]}}Copy the code
file-loader

File resource loader, load img, font, need to configure publicPath

module.exports = {
  ...
  // Specify the file loading directory
  publicPath: 'dist/'.module: {
    rules: [{test: /.png$/,
        use: 'file-loader'}}}]Copy the code
url-loader

Use Data URLs to represent files, such as Data: Base64, for small files

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /.png$/,
        use: 'url-loader'
      }
    ]
	}
}
Copy the code
The difference between file-loader and url-loader
  • Url-loader is used for small files to reduce the number of requests
  • Large files are separately extracted and stored to improve loading speed
module.exports = {
  ...
  module: {
    rules: [{test: /.png$/,
        use: {
          loader: 'url-loader'.options: {
            Files larger than 10KB are stored separately. Files smaller than 10KB are converted into Data URLs embedded in the code
            limit: 10 * 1024 // 10KB }}}]}}Copy the code
babel-loader

The Babel core module @babel/core is installed, and the set of @babel/ PRESET -env is recommended

module.exports = { ... Module: {rules: [{test: /.js$/, use: {loader: 'babel-loader', options: { ['@babel/preset-env'] } } } ] } }Copy the code
html-loader

HTML file loader

module.exports = {
  ...
  module: {
    rules: [{test: /.html$/,
        use: {
          loader: 'html-loader'.options: {
          	// Specify the img SRC attribute and the href attribute of the a link
            attrs: ['img:src'.'a:href'}}}]}}Copy the code
How the Webpack module is loaded
  • Follow the ES Modules standard import declaration
  • Follow the CommonJS standard require function
  • Follow AMD standard define function and require function
  • The @import directive and URL function in the style code
  • The SRC attribute of the image tag in HTML code

2. Webpack develops a loader

Develop an MD loader

// webpack.config.js
module.exports = {
  mode: 'none'.entry: './src/main.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist'),
    publicPath: 'dist/'
  },
  module: {
    rules: [{test: /.md$/,
        use: [
          'html-loader'.'./markdown-loader'}]}}// markdown-loader.js
const marked = require('marked')
module.exports = source= > {
  // console.log(source);
  // return 'console.log("hello ~")'
  const html = marked(source)
  // 1. Return a js code
  // return `module.exports = ${JSON.stringify(html)}`
  // return `export default ${JSON.stringify(html)}`
  // 2. Return an HTML string for the next loader to process
  return html
}
Copy the code

3. The webpack plug-in

Enhance webpack automation, loader is responsible for resource loading, plugin is responsible for automation, such as clearing dist directory, copying static resources to output directory, compression output code

  • Clean-webpack-plugin Automatically cleans up the package directory plug-ins

    // yarn add clean-webpack-plugin --dev
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    module.exports = {
      ...
      plugins: [
        new CleanWebpackPlugin()
      ]
    }
    Copy the code
  • Html-webpack-plugin automatically generates HTML plug-ins that use packaged results

    // yarn add html-webpack-plugin --dev
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    module.exports = {
      ...
      plugins: [
        new CleanWebpackPlugin(),
        // Generate HTML,index.html from the template
        new HtmlWebpackPlugin({
          // Customize the output HTML
          title: 'Webpack Plugin Sample'.meta: {
            viewport: 'width=device-width'
          },
          // Template file
          template: './src/index.html'
        }), 
        // Generate other HTML at the same time, about.html
        new HtmlWebpackPlugin({
          filename: 'about.html']}}),Copy the code
  • Copy-webpack-plugin Plugin used to copy static resources

    // yarn add copy-webpack-plugin --dev
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    module.exports = {
      mode: 'none'.entry: './src/main.js'.output: {
        filename: 'bundle.js'.path: path.join(__dirname, 'dist'),
        // publicPath: 'dist/'
      },
      module: {... },plugins: [
        new CleanWebpackPlugin(),
        // Generate HTML from the template.
        new HtmlWebpackPlugin({
          title: 'Webpack Plugin Sample'.meta: {
            viewport: 'width=device-width'
          },
          template: './src/index.html'
        }), 
        // copy all files in public to dist directory
        new CopyWebpackPlugin([
          'public'])]}Copy the code
  • conclusion

    Extensions are implemented by mounting functions in lifecycle hooks

    // Custom plug-in
    class MyPlugin {
      apply (compiler) {
        console.log('myplugin qidong');
        compiler.hooks.emit.tap('MyPlugin'.compilation= > {
          for (let key in compilation.assets) {
            // console.log(key);
            console.log(compilation.assets[key].source());
            if (key.endsWith('.js')) {
              const contents = compilation.assets[key].source()
              const withoutComments = contents.replace(/\/\*\*+\*\//g.' ')
              compilation.assets[key] = {
                source: () = > withoutComments,
                size: () = > withoutComments.length
              }
            }
          }
        })
      }
    }
    Copy the code

Fourth, build modern Web applications based on modular tools

1. Automatic compilation — Watch

yarn webpack --watch
Copy the code

2. Automatically refresh the browser –webpack-dev-server

Yarn webpack-dev-server --open Open the browserCopy the code

3. Static resource access –devServer configuration

Module. exports = {devServer: {// Specify additional static resource paths, strings or arrays contentBase: './public'}}Copy the code

4. DevServer agent

If you’re using webpack-cli 4 or webpack 5, change webpack-dev-server to webpack serve

module.exports = {
  devServer: {
  	// Specify additional static resource paths, strings, or arrays
  	contentBase: './public'.proxy: {
      '/api': {
        // http://localhost:8080/api/user ---> https://api.github.com/api/users
        target: 'https://api.github.com'.pathRewrite: {
          '^/api': ' '
        },
        // Do not use the native host name
        changeOrigin: true}}}}Copy the code

5. Source-map source code map

module.exports = {
  // Configure assistance tools during development
  devtool: 'source-map'
}
Copy the code

5.1 Source-Map Comparison of Different Types:

5.2 Source-map Selection:

  • Dev environment:cheap-module-eval-source-map
    • Each line of code is no more than 80 characters long, and one line can be put down and easily located. — cheap
    • After loader conversion, the difference is large. — module
    • It doesn’t matter if you pack slowly the first time, you pack faster when you rewrite. — e va l
  • The prod environment:none / nosources-source-map
    • It would expose our source code

6.HMR: hot-module-replace Hot update

The logic of module hot replacement needs to be handled manually.

// Enable hot replace
const webpack = require('webpack')
module.exports = {
  ...
  devServer: {
    hot: true}...plugins: [
  	new webpack.HotModuleReplacementPlugin()
  ]
}

// main.js
let lastM = heading
// Handle hot replacement
if (module.hot) {
   module.hot.accept('./heading'.() = > {
    // Custom content
    const val = lastM.innerHTML
    document.removeChild(lastM)
    const newM = new heading()
    newM.innerHTML = val 
    document.appendChild(newM)
    lastM = newM
  })
}
Copy the code
  • hotOnlyreplacehot, exposure error
  • module.hotDo judgment
  • Heat treatment logic is not packaged into the project

7.DefinePlugin injects global variables into code

module.exports = {
  ...
  plugins: [
    new webpack.DefinePlugin({
      API_BASE_URL: JSON.Stringify("https://XXX") // Code snippet,'"https://XXX"'}})]Copy the code

Five, packaging tool optimization skills

1. Tree-shaking

This function is automatically enabled in production mode, and dead-code is removed

// Tree-shaking implementation
module.exports = {
	optimization: {
    usedExports: true.// Export only the members that are used externally
    minimize: true // Compress the code}},Copy the code

Note:

If babel-loader uses THE preset-env function to transform JS (ES Modules —-> CommonJS), tree-shaking is invalid. The latest version does not turn on conversion by default, so it will not fail.

module: {
    rules: [{test: /.js$/,
        use: {
          loader: 'babel-loader'.options: {
            presets: [['@babel/preset-env', { modules: true }] // Sets whether conversion to ES is enabled]}}},]}Copy the code

2. Scope-merge module functions

// Tree-shaking implementation
module.exports = {
	optimization: {
    usedExports: true.// Export only the members that are used externally
    concatenateModules: true.// Merge modules into one to reduce code size
    // minimize: true, // compress code}},Copy the code

I have sideEffexts

Side effect: What the module does when it executes other than exporting members. This is typically used when developing NPM modules

Advantages: Provides more compression space.

// index.js
export a from './a'
export b from './b' // This is superfluous for main, resulting in sideEffexts
export c from './c' // This is superfluous for main, resulting in sideEffexts

// main.js
import a from './index'

// webpack.config.js
module.exports = {
	optimization: {
    sideEffects: true}},// package.json{...// "sideEffects": false, // make sure there are no sideEffects in the project
  "sideEffects": [
    "./src/a.js".// Flag which modules have side effects and will not be shaken away by tree-shaking
    "*.css"]}Copy the code

4. Code-splitting

4.1 Multi-entry packaging
module.exports = {
	entry: {
     a: './a.js'.b: './b.js'
  },
  output: {
    filename: '[name].bundle.js' // [name] placeholder content
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'a'./ / title
      template: './src/a.html'./ / template
      filename: 'a.html'./ / file name
      chunks: ['a'] // set chunks separately, and HTML will only introduce the corresponding packaged JS files
    }),
    new HtmlWebpackPlugin({
      title: 'b'.template: './src/b.html'.filename: 'b.html'.chunks: ['b'] // set chunks separately, and HTML will only introduce the corresponding packaged JS files]}}),Copy the code
4.2 Split chunks Extract common modules
module.exports = {
	optimization: {
    splitChunks: {
      chunks: 'all' // All public modules are extracted separately into a file}}},Copy the code
4.3 Loading on Demand
// Load on demand using dynamic import. Dynamically imported modules are automatically subcontracted
import('/a').then({ a } => {
  // Process logic. })import('/b').then({ b } => {
  // Process logic. })// Vue and React can use the lazy loading mode of dynamic routes
Copy the code
4.4 Magic Comments
// Add a comment to the import position, and the same chunkname will be packaged together
import(/* webpackChunkName: 'a' */'/a').then({ a } => {
  // Process logic. })import(/* webpackChunkName: 'b' */'/b').then({ b } => {
  // Process logic. })Copy the code
4.5 MiniCssExtractPlugin Extracts CSS to a single file
// yarn add mini-css-exract-plugin --dev
const MiniCssExtractPlugin = require('mini-css-exract-plugin')
module.exports = {
	...
  module: {
    rules: [{test: /.css$/,
        use: [
          // 'style-loader', // inject the style through the style tag
          // This is recommended when CSS volume exceeds 150K
          MiniCssExtractPlugin.loader, // If MiniCssExtractPlugin is used, style-loader is useless
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin()
  ]
}
Copy the code
4.6 optimize- CSS-assets -webpack-plugin CSS compression plugin
// yarn add mini-css-exract-plugin --dev
// yarn add optimize-css-assets-webpack-plugin --dev
// yarn add terser-webpack-plugin --dev js compression plugin
const MiniCssExtractPlugin = require('mini-css-exract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
	...
  / / new OptimizeCssAssetsWebpackPlugin () in the here, only when the minimizer open to perform, that is, when the production environment
  optimization: {
    // A custom compression plug-in overwrites the original plug-in
     minimizer: [
       new TerserWebpackPlugin(), / / js compressed
       new OptimizeCssAssetsWebpackPlugin() / / CSS compression]},module: {
    rules: [{test: /.css$/,
        use: [
          // 'style-loader', // inject the style through the style tag
          // This is recommended when CSS volume exceeds 150K
          MiniCssExtractPlugin.loader, // If MiniCssExtractPlugin is used, style-loader is useless
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin(),
    // CSS file compression. All environments perform this compression code
    // new OptimizeCssAssetsWebpackPlugin()]}Copy the code
4.7 Output File name Hash
  • [name]-[hash: 8]At the project level, the hash value changes as long as one place is changed,8:Specifying the hash length
  • [name]-[chunkhash: 8] The ones under the same chunk change,8:Specifying the hash length
  • [name]-[contenthash: 8]File level hash, different hash for different files,8:Specifying the hash length

[Name]-[Contenthash: 8] Best choice

// Static resources can be cached
const MiniCssExtractPlugin = require('mini-css-exract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
  output: {
    // filename: '[name]-[hash].bundle.js' // [name]-[hash] Item level. Hash value changes if one place is changed
    // filename: '[name]-[chunkhash].bundle.js' // [name]-[chunkhash] Changes under the same chunk
    filename: '[name]-[contenthash].bundle.js' // [name]-[contenthash] Hash file level. Different files use different hashes},.../ / new OptimizeCssAssetsWebpackPlugin () in the here, only when the minimizer open to perform, that is, when the production environment
  optimization: {
    // A custom compression plug-in overwrites the original plug-in
     minimizer: [
       new TerserWebpackPlugin(), / / js compressed
       new OptimizeCssAssetsWebpackPlugin() / / CSS compression]},module: {
    rules: [{test: /.css$/,
        use: [
          // 'style-loader', // inject the style through the style tag
          // This is recommended when CSS volume exceeds 150K
          MiniCssExtractPlugin.loader, // If MiniCssExtractPlugin is used, style-loader is useless
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin({
      filename: '[name]-[hash].bundle.css'
    }),
    // CSS file compression. All environments perform this compression code
    // new OptimizeCssAssetsWebpackPlugin()]}Copy the code