Webpack教程

Webpack教程

1.webpack简介

1.1 webpack是什么

​ 入口文件例如index.js,依次引入资源例如jQuery,less等,引入之后形成了 chunk 代码块

​ 再对chunk代码块进行处理,即打包(less打包成css,jQuery变成js文件等),打包好的资源就称为bundle

1.2 webpack五个核心概念:Entry、Output、Loader、Plugins、Mode

2.webpack 初体验

2.1 初始化配置
1. 运行指令:
  开发环境:webpack ./src/index.js -o ./build/built.js --mode=development
    webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js
    整体打包环境,是开发环境
  生产环境:webpack ./src/index.js -o ./build/built.js --mode=production
    webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js
    整体打包环境,是生产环境
    
 2. 结论:
    1. webpack能处理js/json资源,不能处理css/img等其他资源
    2. 生产环境和开发环境将ES6模块化编译成浏览器能识别的模块化~
    3. 生产环境比开发环境多一个压缩js代码。
2.2 打包样式资源

​ webpack配置文件:webapck.config.js

​ 配置内容如下:

   const {resolve}   =  require(‘path’);
   module.exports={
	entry:'./src/js/index.js'  //入口文件
	output:{
       filename:'./built.js',   //输出文件名
       path:resolve(_dirname, 'build') //输出文件路径配置
        // __dirname nodejs的变量,代表当前文件的目录绝对路径
        // 输出到build文件夹下,名字为filename名字
   },
   mode: {
      rules: [
      // 详细loader配置
      // 不同文件必须配置不同loader处理
      {
        // 匹配哪些文件
        test: /\.css$/,
        // 使用哪些loader进行处理
        use: [
          // use数组中loader执行顺序:从右到左,从下到上 依次执行
          // 创建style标签,将js中的样式资源插入进行,添加到head中生效
          'style-loader',
          // 将css文件变成commonjs模块加载js中,里面内容是样式字符串
          'css-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          // 将less文件编译成css文件
          // 需要下载 less-loader和less
          'less-loader'
        ]
      }
    ]
  },
  // plugins的配置
  plugins: [
    // 详细plugins的配置
  ],
  // 模式
  mode: 'development', // 开发模式
  // mode: 'production'  // 生产环境  会压缩代码,一般开发不用
}
}

​ 所有构建工具都是基于nodejs平台运行的模块,默认采用commonjs

补充:commonjs

​ Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

module对象
    1.module.exports属性

module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。`

    2.exports变量`

`node为每一个模块提供了一个exports变量(可以说是一个对象),指向 module.exports。这相当于每个模块中都有一句这样的命令 var exports = module.exports;`

`这样,在对外输出时,可以在这个变量上添加方法。例如 exports.add = function (r){return Math.PI * r *r};注意:不能把exports直接指向一个值,这样就相当于切断了 exports 和module.exports 的关系。例如 exports=function(x){console.log(x)};`

`一个模块的对外接口,就是一个单一的值,不能使用exports输出,必须使用 module.exports输出。module.exports=function(x){console.log(x);};` 
2.3 打包html资源

plugins: 1.下载 2.引入 3. 使用 区分: loader:1.下载 2. 使用(配置loader)

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
    ]
  },
  plugins: [
    // plugins的配置
    // html-webpack-plugin
    // 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
    // 需求:需要有结构的HTML文件
    new HtmlWebpackPlugin({
      // 复制 './src/index.html' 文件,并自动引入打包输出的所有资源(JS/CSS)
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
2.4 打包图片资源
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        // 要使用多个loader处理用use
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 问题:默认处理不了html中img图片
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        // 使用一个loader
        // 下载 url-loader file-loader
        loader: 'url-loader',
        options: {
          // 图片大小小于8kb,就会被base64处理,base64类似一种字符串
          // 优点: 减少请求数量(减轻服务器压力)
          // 缺点:图片体积会更大(文件请求速度更慢)
          limit: 8 * 1024,
          // 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
          // 解析时会出问题:[object Module]
          // 解决:关闭url-loader的es6模块化,使用commonjs解析
          esModule: false,
          // 给图片进行重命名
          // [hash:10]取图片的hash的前10位
          // [ext]取文件原来扩展名
          name: '[hash:10].[ext]'
        }
      },
      {
        test: /\.html$/,
        // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
        loader: 'html-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
2.5 打包其他资源
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');


module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      // 打包其他资源(除了html/js/css资源以外的资源)
      {
        // 排除css/js/html资源
        exclude: /\.(css|js|html|less)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
2.6 devServer

下载包 webpack-dev-server 本地安装的话,指令需要npx ,如果全局安装 直接webpack-dev-sever就行

mode: 'development',

// 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
// 特点:只会在内存中编译打包,不会有任何输出
// 启动devServer指令为:npx webpack-dev-server
devServer: {
  // 项目构建后路径
  contentBase: resolve(__dirname, 'build'),
  // 启动gzip压缩
  compress: true,
  // 端口号
  port: 3000,
  // 自动打开浏览器
  open: true
}

​ 运行指令:npx webpack-dev-server 之后会维持程序运行,然后自动编译更新,浏览器会自动更新

2.7 开发环境配置

​ 整合之前的模块,运行指令: npx webpack-dev-server

​ url-loader是一种基于file-loader的优化

​ 输出到一个目录里面,outputPath:‘xxx’

/*
  开发环境配置:能让代码运行
    运行项目指令:
      webpack 会将打包结果输出出去
      npx webpack-dev-server 只会在内存中编译打包,没有输出
*/

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true
  }
};

3.webpack生产环境的基本配置

3.1提取css成单独文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 创建style标签,将样式放入
          // 'style-loader', 
          // 这个loader取代style-loader。作用:提取js中的css成单独文件
          MiniCssExtractPlugin.loader,
          // 将css文件整合到js文件中
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      // 对输出的css文件进行重命名
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};
3.2 css兼容性问题
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 设置nodejs环境变量
// process.env.NODE_ENV = 'development';

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          /*
            css兼容性处理:postcss --> postcss-loader postcss-preset-env

            帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式

            "browserslist": {
              // 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
              "development": [
                "last 1 chrome version",
                "last 1 firefox version",
                "last 1 safari version"
              ],
              // 生产环境:默认是看生产环境
              "production": [
                ">0.2%",
                "not dead",
                "not op_mini all"
              ]
            }
          */
          // 使用loader的默认配置
          // 'postcss-loader',
          // 修改loader的配置
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                // postcss的插件
                require('postcss-preset-env')()
              ]
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    })
  ],
  mode: 'development'
};
3.3 压缩css

​ 使用插件 optimize-css-assets-webpack-plugin

const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    // 压缩css
    new OptimizeCssAssetsWebpackPlugin()
  ]

3.4 Js语法检查
  依赖的库,eslint-loader  eslint
module: {
  rules: [
    /*
      语法检查: eslint-loader  eslint
        注意:只检查自己写的源代码,第三方的库是不用检查的
        设置检查规则:
          package.json中eslintConfig中设置~
            "eslintConfig": {
              "extends": "airbnb-base"
            }
          airbnb --> eslint-config-airbnb-base  eslint-plugin-import eslint
    */
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'eslint-loader',
      options: {
        // 自动修复eslint的错误
        fix: true
      }
    }
  ]
},
3.5 Js兼容性处理
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      /*
        js兼容性处理:babel-loader @babel/core 
          1. 基本js兼容性处理 --> @babel/preset-env
            问题:只能转换基本语法,如promise高级语法不能转换
          2. 全部js兼容性处理 --> @babel/polyfill  
            问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了~
          3. 需要做兼容性处理的就做:按需加载  --> core-js
      */  
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 预设:指示babel做怎么样的兼容性处理
          presets: [
            [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定core-js版本
                corejs: {
                  version: 3
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
                  chrome: '60',
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17'
                }
              }
            ]
          ]
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development'
};
3.6 Js和HTML的压缩

​ JS的压缩,mode改成production

  // 生产环境下会自动压缩js代码
  mode: 'production'
};

​ HTML的压缩:

plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      // 压缩html代码
      minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true
      }
    })
  ],
  mode: 'production'
};
3.7 生产环境配置
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [...commonCssLoader]
      },
      {
        test: /\.less$/,
        use: [...commonCssLoader, 'less-loader']
      },
      /*
        正常来讲,一个文件只能被一个loader处理。
        当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
          先执行eslint 在执行babel
      */
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,  // 排除其他不检测的,只检测自己的代码
        loader: 'babel-loader', 
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ]
        }
      },
      {
        test: /\.(jpg|png|gif)/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          outputPath: 'imgs',
          esModule: false
        }
      },
      {
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        exclude: /\.(js|css|less|html|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production'
};

4 webpack 优化配置

webpack性能优化:开发环境性能优化,生产环境性能优化
开发环境性能优化:优化打包构建速度(HMR),优化代码调试(source-map)
生产环境性能优化:
优化打包构建速度(oneOf、缓存–babel缓存),多进程打包(并行打包,一般用于babel)
优化代码运行的性能:
缓存 hash-chunkhash–contenthash
tree shaking–es6模块 production默认启动 sideEffect
code split :单入口 —> optimization、import xxx 语法、dll
多入口 —>
lazy loading 懒加载/预加载:懒加载触发再加载,预加载空闲时加载(兼容性问题严重)
pwa:离线也能访问
externals:
dll:

​ 三个hash的区别:

​ hash指的是webpack每次打包时都会生成唯一的hash值,文件不管会不会变,hash一定会变,—这会导致文件没改变,打包却重新打包了

​ chunkhash:来自入同一个入口则属于同一个chunk,同属于一个chunk则共享一个hash值,–js和css同属于一个chunk,这样的话,js变了,css也得重新打包

​ contenthash:根据文件内容生成hash,如果内容不变,contenthash就不会变

4.1 开发环境性能优化—优化打包构建速度(HMR) hot module replacement

​ 热模块替换 作用:一个模块发生变化,只会重新打包这一个模块,极大提升构建速度

​ 样式文件:可以使用HMR功能,style-loader内部实现了

​ js文件:默认不能使用HMR功能 ----> 需要修改js代码,添加支持HMR功能的代码

​ 注意:HMR功能对js的处理,只能处理非入口js文件的其他文件

​ 在index.js中添加

if (module.hot) {
  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
  module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();  // print是后面的回调函数
  });
}

​ html文件:默认不能使用HMR功能,同时会导致问题:html不能热更新了(不用做HMR功能)

​ 解决:entry入口修改,将html文件引入

			entry: ['./src/js/index.js', './src/index.html'],
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }
};

4.2 source-map :一种提供源代码到构建后代码映射 技术(如果构建后代码出错,映射可以追踪源代码错误)
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    hot: true
  },
  devtool: 'eval-source-map'
};
/*
  source-map: 一种 提供源代码到构建后代码映射 技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)

    [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

    source-map:外部
      错误代码准确信息 和 源代码的错误位置
    inline-source-map:内联
      只生成一个内联source-map
      错误代码准确信息 和 源代码的错误位置
    hidden-source-map:外部
      错误代码错误原因,但是没有错误位置
      不能追踪源代码错误,只能提示到构建后代码的错误位置
    eval-source-map:内联
      每一个文件都生成对应的source-map,都在eval
      错误代码准确信息 和 源代码的错误位置
    nosources-source-map:外部
      错误代码准确信息, 但是没有任何源代码信息
    cheap-source-map:外部
      错误代码准确信息 和 源代码的错误位置 
      只能精确到行
    cheap-module-source-map:外部
      错误代码准确信息 和 源代码的错误位置 
      module会将loader的source map加入

    内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快

    开发环境:速度快,调试更友好
      速度快(eval>inline>cheap>...)
        eval-cheap-souce-map
        eval-source-map
      调试更友好  
        souce-map
        cheap-module-souce-map
        cheap-souce-map

     推荐 --> eval-source-map  / eval-cheap-module-souce-map

    生产环境:源代码要不要隐藏? 调试要不要更友好
      内联会让代码体积变大,所以在生产环境不用内联
      nosources-source-map 全部隐藏
      hidden-source-map 只隐藏源代码,会提示构建后代码错误信息

      推荐--> source-map / cheap-module-souce-map
*/
4.3 oneOf

​ 由于每个文件都得过一遍loader,比如css文件,要是第一个匹配到后应该就直接退出,不用完全遍历完。

​ 注意:不能有两个配置处理同一种类型文件,例如里面有两个js处理,解决方法:将其中的一个js loader提取出去,放到oneOf数组外面,

// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
	
]
4.4 缓存
针对babel的缓存优化:直接配置 cacheDirectory: true
  test: /\.js$/,
  exclude: /node_modules/,
  loader: 'babel-loader',
  options: {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: 'usage',
          corejs: { version: 3 },
          targets: {
            chrome: '60',
            firefox: '50'
          }
        }
      ]
    ],
    // 开启babel缓存
    // 第二次构建时,会读取之前的缓存
    cacheDirectory: true
  }
},

server.js 服务器代码(用来看缓存)

/*
  服务器代码
  启动服务器指令:
    npm i nodemon -g
    nodemon server.js

    node server.js
  访问服务器地址:
    http://localhost:3000

*/
const express = require('express');

const app = express();
// express.static向外暴露静态资源
// maxAge 资源缓存的最大时间,单位ms
app.use(express.static('build', { maxAge: 1000 * 3600 }));

app.listen(3000);

​ 资源名称变了才会重新加载请求,没变会直接加载本地缓存,所以为了避免这种问题,可以在命名的地方加哈希值命名 -----> 修改源代码之后,新的哈希值生成,从而会产生新的文件

文件资源缓存:利用hash:每次webpack构建时都会生成一个唯一的hash值

​ 问题:因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效

    缓存:
    babel缓存
      cacheDirectory: true
      --> 让第二次打包构建速度更快
    文件资源缓存
      hash: 每次wepack构建时会生成一个唯一的hash值。
        问题: 因为js和css同时使用一个hash值。
          如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
    chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
      问题: js和css的hash值还是一样的
        因为css是在js中被引入的,所以同属于一个chunk
    contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样    
    --> 让代码上线运行缓存更好使用
4.5 tree shaking 树摇

​ 去除应用程序中未使用的 代码,在production生产模式下会默认开启

/*
  tree shaking:去除无用代码
    前提:1. 必须使用ES6模块化  2. 开启production环境
    作用: 减少代码体积

    在package.json中配置 
      "sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
        问题:可能会把css / @babel/polyfill (副作用)文件干掉
      "sideEffects": ["*.css", "*.less"]
*/
4.6 code split 代码分割

​ 大文件拆分,实现并行加载,加快速度, 也可以实现按需加载

方法一:入口拆分
// 单入口
// entry: './src/js/index.js',
entry: {
  // 多入口:有一个入口,最终输出就有一个bundle
  index: './src/js/index.js',
  test: './src/js/test.js'
},
  output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
方法二:利用optimization对node_modules单独打包一个chunk输出
/*   在module.exports里输入 optimization
  1. 可以将node_modules中代码单独打包一个chunk最终输出
  2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
  	例如共用jQuery文件等
*/
optimization: {
  splitChunks: {
    chunks: 'all'
  }
},
方法三:通过js代码,让某个文件被单独打包成一个chunk
/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test')
  .then(({ mul, count }) => {
    // 文件加载成功~  加载test.js里面的内容
    // eslint-disable-next-line
    console.log(mul(2, 5));
  })
  .catch(() => {
    // eslint-disable-next-line
    console.log('文件加载失败~');
  });

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
4.7 lazy loading 懒加载和预加载

​ 懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

懒加载:运用代码分割的思路,将import 语法 写入一个异步函数中,当满足异步函数的触发条件时,再懒加载代码
//当文件需要使用时才加载

document.getElementById('btn').onclick = function() {
  // 懒加载~:当文件需要使用时才加载~
  // 预加载 prefetch:会在使用之前,提前加载js文件 
  // 正常加载可以认为是并行加载(同一时间加载多个文件)  
  // 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });
};

预加载:webpackPrefetch:true 会在使用之前,提前加载js文件
// 正常加载可以认为是并行加载(同一时间加载多个文件)
//预加载:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源 (兼容性比较差)

4.8 PWA:可以让网页像APP一样离线也可以访问,性能也会更好

​ 渐进式网络应用程序(progressive web application - PWA),是一种可以提供类似于native app(原生应用程序) 体验的 web app(网络应用程序)。PWA 可以用来做很多事。其中最重要的是,在离线(offline)时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的 web 技术来实现的。
​ 利用Workbox -----> workbox-webpack-plugin
在plugins里面直接

    new WorkboxWebpackPlugin.GenerateSW({
      /*
        1. 帮助serviceworker快速启动
        2. 删除旧的 serviceworker

        生成一个 serviceworker 配置文件~
      */
      clientsClaim: true,
      skipWaiting: true
    })
  ],
  mode: 'production',
  devtool: 'source-map'
};

​ 1.帮助serviceworker 快速启动

​ 2.删除 旧的serviceworker

生成一个serviceworker配置文件
后面通过这个serviceworker配置文件去注册serviceworker

clientsClaim:true,
skipWaiting: true	

)

注册serviceworker:入口js中
处理兼容性问题:
if(‘serviceWorker’in navigator){
	
}

注意:1.eslint不认识window、navigator等全局变量
          解决:需要修改package.json中eslintConfig配置:
	''env'':{
	''browser'': true  // 支持浏览器端全局变量
	 //  "node": true  // 支持nodejs环境变量
	}

           2. serviceworker代码必须运行在服务器上
	----> nodejs
	----> 
	    npm i serve -g
                    serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
4.9 多进程打包:thread-loader 一般是给babel用的

​ 在babel里使用 use :

use: [
  /* 
    开启多进程打包。 
    进程启动大概为600ms,进程通信也有开销。
    只有工作消耗时间比较长,才需要多进程打包
  */
  {
    loader: 'thread-loader',
    options: {
      workers: 2 // 进程2个
    }
  },
  {
    loader: 'babel-loader',
    options: {
      presets: [
        [
          '@babel/preset-env',
          {
            useBuiltIns: 'usage',
            corejs: { version: 3 },
            targets: {
              chrome: '60',
              firefox: '50'
            }
          }
        ]
      ],
      // 开启babel缓存
      // 第二次构建时,会读取之前的缓存
      cacheDirectory: true
    }
  }
]

​ 进程开启大概为600ms,进程通信也有开销,只有工作消耗时间比较长才需要多进程打包

4.10 externals:防止将某些包打包到最终输出的babel中

忽略掉打包,但是记得需要在html中用script方式引入 用cdn

externals: {
  // 拒绝jQuery被打包进来
  jquery: 'jQuery'
}
4.11 dll 动态链接库 dynami link library

​ 将库拆开打包成不同的chunk,单独打包需要新的配置文件,例如,webpack.dll.js

/*
  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
    当你运行 webpack 时,默认查找 webpack.config.js 配置文件
    需求:需要运行 webpack.dll.js 文件
      --> webpack --config webpack.dll.js   // 不要运行webpack.config.js文件
*/

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery'],
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个 manifest.json --> 提供和jquery映射
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};

在webpack.dll.js运行完之后,之后在webpack.config.js中就可以

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    // 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    // 将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
      // __dirname 当前目录绝对路径
    })
  ],
  mode: 'production'
};
区分:externals是彻底不打包,通过cdn链接,dll需要打包,只需要打包一次,如果自己服务器暴露出去,建议用dll

5.webpack配置详解

5.1 entry
/*
  entry: 入口起点
    1. string --> './src/index.js'
      单入口
      打包形成一个chunk。 输出一个bundle文件。
      此时chunk的名称默认是 main
    2. array  --> ['./src/index.js', './src/add.js']
      多入口
      所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
        --> 只有在HMR功能中让html热更新生效~
    3. object
      多入口
      有几个入口文件就形成几个chunk,输出几个bundle文件
      此时chunk的名称是 key

      --> 特殊用法
        {
          // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
          index: ['./src/index.js', './src/count.js'], 
          // 形成一个chunk,输出一个bundle文件。
          add: './src/add.js'
        }
*/
5.2 output
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    // 文件名称(指定名称+目录)
    filename: 'js/[name].js',
    // 输出文件目录(将来所有资源输出的公共目录)
    path: resolve(__dirname, 'build'),
    // 所有资源引入公共路径前缀 --> 'imgs/a.jpg' --> '/imgs/a.jpg'
    publicPath: '/',  // 设置了之后所有的前面都加了这个前缀再去寻找
    chunkFilename: 'js/[name]_chunk.js', // 非入口chunk的名称  
    	// entry指定的入口文件就叫入口chunk,只要不是单入口和多入口,额外的chunk都是它来命名
    	// 分割chunk方法: import 方法文件单独分割,optimization将node module里的东西分割成
    // library: '[name]', // 整个库向外暴露的变量名
    // libraryTarget: 'window' // 变量名添加到哪个上 browser
    // libraryTarget: 'global' // 变量名添加到哪个上 node
    // libraryTarget: 'commonjs'
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'development'
};
5.3 module
module: {
  rules: [
    // loader的配置
    {
      test: /\.css$/,
      // 多个loader用use
      use: ['style-loader', 'css-loader']
    },
    {
      test: /\.js$/,
      // 排除node_modules下的js文件
      exclude: /node_modules/,
      // 只检查 src 下的js文件
      include: resolve(__dirname, 'src'),
      // 优先执行
      enforce: 'pre',
      // 延后执行
      // enforce: 'post',
      // 单个loader用loader
      loader: 'eslint-loader',
      options: {}
    },
    {
      // 以下配置只会生效一个
      oneOf: []
    }
  ]
},
5.4 resolve
// 解析模块的规则
resolve: {
  // 配置解析模块路径别名: 优点简写路径 缺点路径没有提示
  alias: {
    $css: resolve(__dirname, 'src/css')
  },
  // 配置省略文件路径的后缀名
  extensions: ['.js', '.json', '.jsx', '.css'],
  // 告诉 webpack 解析模块是去找哪个目录
  modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}
5.5 dev server
devServer: {
  // 运行代码的目录
  contentBase: resolve(__dirname, 'build'),
  // 监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
  watchContentBase: true,
  watchOptions: {
    // 忽略文件
    ignored: /node_modules/
  },
  // 启动gzip压缩
  compress: true,
  // 端口号
  port: 5000,
  // 域名
  host: 'localhost',
  // 自动打开浏览器
  open: true,
  // 开启HMR功能
  hot: true,
  // 不要显示启动服务器日志信息
  clientLogLevel: 'none',
  // 除了一些基本启动信息以外,其他内容都不要显示
  quiet: true,
  // 如果出错了,不要全屏提示~
  overlay: false,
  // 服务器代理 --> 解决开发环境跨域问题
  proxy: {
    // 一旦devServer(5000)服务器接受到 /api/xxx 的请求,就会把请求转发到另外一个服务器(3000)
    '/api': {
      target: 'http://localhost:3000',
      // 发送请求时,请求路径重写:将 /api/xxx --> /xxx (去掉/api)
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}
5.6 optimization
optimization: {
  splitChunks: {
    chunks: 'all'
    // 默认值,可以不写~
    /* minSize: 30 * 1024, // 分割的chunk最小为30kb
    maxSiza: 0, // 最大没有限制
    minChunks: 1, // 要提取的chunk最少被引用1次
    maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
    maxInitialRequests: 3, // 入口js文件最大并行请求数量
    automaticNameDelimiter: '~', // 名称连接符
    name: true, // 可以使用命名规则
    cacheGroups: {
      // 分割chunk的组
      // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
      // 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        // 优先级
        priority: -10
      },
      default: {
        // 要提取的chunk最少被引用2次
        minChunks: 2,
        // 优先级
        priority: -20,
        // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
        reuseExistingChunk: true
      } 
    }*/
  },
  // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
  // 解决:修改a文件导致b文件的contenthash变化
  runtimeChunk: {
    name: entrypoint => `runtime-${entrypoint.name}`
  },
  minimizer: [
    // 配置生产环境的压缩方案:js和css
    new TerserWebpackPlugin({
      // 开启缓存
      cache: true,
      // 开启多进程打包
      parallel: true,
      // 启动source-map
      sourceMap: true
    })
  ]
}

6.webpack5使用

webpack5

此版本重点关注以下内容:

  • 通过持久缓存提高构建性能.
  • 使用更好的算法和默认值来改善长期缓存.
  • 通过更好的树摇和代码生成来改善捆绑包大小.
  • 清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改.
  • 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用 v5.

下载

  • npm i webpack@next webpack-cli -D

自动删除 Node.js Polyfills

早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,许多模块用途现在主要是为前端目的而编写的。webpack <= 4 附带了许多 node.js 核心模块的 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用。

尽管这使使用为 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中。在许多情况下,这些 polyfill 是不必要的。

webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块。

迁移:

  • 尽可能尝试使用与前端兼容的模块。
  • 可以为 node.js 核心模块手动添加一个 polyfill。错误消息将提示如何实现该目标。

Chunk 和模块 ID

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

chunkIds: "deterministic", moduleIds: "deterministic"

Chunk ID

你可以不用使用 import(/* webpackChunkName: "name" */ "module") 在开发环境来为 chunk 命名,生产环境还是有必要的

webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2)命名了

Tree Shaking

  1. webpack 现在能够处理对嵌套模块的 tree shaking
// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from './inner';
export { inner };

// user.js
import * as module from './module';
console.log(module.inner.a);

在生产环境中, inner 模块暴露的 b 会被删除

  1. webpack 现在能够多个模块之前的关系
import { something } from './something';

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}

当设置了"sideEffects": false时,一旦发现test方法没有使用,不但删除test,还会删除"./something"

  1. webpack 现在能处理对 Commonjs 的 tree shaking

Output

webpack 4 默认只能输出 ES5 代码

webpack 5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码.

如:output.ecmaVersion: 2015

SplitChunk

// webpack4
minSize: 30000;
// webpack5
minSize: {
  javascript: 30000,
  style: 50000,
}

Caching

// 配置缓存
cache: {
  // 磁盘存储
  type: "filesystem",
  buildDependencies: {
    // 当配置修改时,缓存失效
    config: [__filename]
  }
}

缓存将存储到 node_modules/.cache/webpack

监视输出文件

之前 webpack 总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。

此次更新在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。

默认值

  • entry: "./src/index.js
  • output.path: path.resolve(__dirname, "dist")
  • output.filename: "[name].js"

更多内容

https://github.com/webpack/changelog-v5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值