Webpack Dev Server

集成了 主动编译 和 主动刷新浏览器 等性能。webpack-dev-server 在编译之后不会写入到任何输入文件,而是将 bundle 文件保留在内存中,而后将它们 serve 到 server 中。

yarn add webpack-dev-server --dev
const path = require("path");const { CleanWebpackPlugin } = require("clean-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const CopyWebpackPlugin = require("copy-webpack-plugin");module.exports = {  mode: "development",  entry: "./src/main.js",  output: {    filename: "bundle.js",    path: path.join(__dirname, "dist"),  },  module: {    rules: [      {        test: /.css$/,        use: ["style-loader", "css-loader"],      },      {        test: /.png$/,        use: {          loader: "url-loader",          options: {            limit: 10 * 1024,          },        },      },    ],  },  plugins: [    new CleanWebpackPlugin(),    new HtmlWebpackPlugin({      title: "Ideal Webpack Develop Env",      meta: {        viewport: "width=device-width",      },      template: "./src/index.html",    }),    // 开发阶段最好不要应用这个插件,频繁复制动态资源文件    // new CopyWebpackPlugin({    //   patterns: ["public"],    // }),  ],  devServer: {    // 指定额定动态资源文件门路    contentBase: ["./public"],    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": "",        },      },    },  },};
yarn webpack serve --open

Source Map

Source map 就是一个信息文件,外面贮存着地位信息。也就是说,转换后的代码的每一个地位,所对应的转换前的地位。

有了它,出错的时候,除错工具将间接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大不便。

启用 Source map 只须要在转换后的代码尾部加上 //# sourceMappingURL=/path/to/file.js.map

Webpack 中的 source map

const path = require("path");module.exports = {  mode: "development",  entry: "./src/main.js",  output: {    filename: "bundle.js",    path: path.join(__dirname, "dist"),  },  devtool: "source-map",};

罕用模式:

  • eval:只能定位文件,没有生成 source map 文件,构建速度快,不能晓得行列信息
  • eval-source-map:能够定位文件和行列信息
  • eval-cheap-source-map:只能定位到行信息,代码通过 loader 转化
  • eval-cheap-module-source-map:开发环境的源代码

HMR(Hot Module Replacement)

HMR 容许在运行时更新所有类型的模块,而无需齐全刷新

const path = require("path");const { CleanWebpackPlugin } = require("clean-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const CopyWebpackPlugin = require("copy-webpack-plugin");module.exports = {  mode: "development",  entry: "./src/main.js",  output: {    filename: "bundle.js",    path: path.join(__dirname, "dist"),  },  module: {    rules: [      {        test: /.css$/,        use: ["style-loader", "css-loader"],      },      {        test: /.png$/,        use: {          loader: "url-loader",          options: {            limit: 10 * 1024,          },        },      },    ],  },  plugins: [    new CleanWebpackPlugin(),    new HtmlWebpackPlugin({      title: "Ideal Webpack Develop Env",      meta: {        viewport: "width=device-width",      },      template: "./src/index.html",    }),  ],  devServer: {    hot: true,    // hotOnly: true // 只应用 HMR,不会 fallback 到 live reloading  },  devtool: "source-map",};

因为 css 具备对立的规定,只须要笼罩替换款式即可,而且 style-loader 曾经实现过 HMR 的热替换。而 js 文件是没有简单多样的,没有对立的规定,所以须要咱们本人依据理论状况实现

import "./main.css";import createEditor from "./editor";const editor = createEditor();document.body.appendChild(editor);// ================================================================// HMR 手动解决模块热更新// 不必放心这些代码在生产环境冗余的问题,因为通过 webpack 打包后,// 这些代码全副会被移除,这些只是开发阶段用到if (module.hot) {  let hotEditor = editor;  module.hot.accept("./editor.js", () => {    // 当 editor.js 更新,主动执行此函数    // 长期记录编辑器内容    const value = hotEditor.innerHTML;    // 移除更新前的元素    document.body.removeChild(hotEditor);    // 创立新的编辑器    // 此时 createEditor 曾经是更新过后的函数了    hotEditor = createEditor();    // 还原编辑器内容    hotEditor.innerHTML = value;    // 追加到页面    document.body.appendChild(hotEditor);  });  module.hot.accept("./better.png", () => {    // 当 better.png 更新后执行    // 重写设置 src 会触发图片元素从新加载,从而部分更新图片    img.src = background;  });  // style-loader 外部主动解决更新款式,所以不须要手动解决款式模块}

不同环境下的配置

为不同环境创立不同的配置,应用webpack-merge合并雷同的配置

webpack.common.js

const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = {  entry: "./src/main.js",  output: {    filename: "js/bundle.js",  },  module: {    rules: [      {        test: /\.css$/,        use: ["style-loader", "css-loader"],      },      {        test: /\.(png|jpe?g|gif)$/,        use: {          loader: "file-loader",          options: {            outputPath: "img",            name: "[name].[ext]",          },        },      },    ],  },  plugins: [    new HtmlWebpackPlugin({      title: "Webpack Tutorial",      template: "./src/index.html",    }),  ],};

webpack.dev.js

const webpack = require("webpack");const { merge } = require("webpack-merge");const common = require("./webpack.common");module.exports = merge(common, {  mode: "development",  devtool: "source-map",  devServer: {    hot: true,    contentBase: "public",  },  plugins: [new webpack.HotModuleReplacementPlugin()],});

webpack.prod.js

const { merge } = require("webpack-merge");const { CleanWebpackPlugin } = require("clean-webpack-plugin");const CopyWebpackPlugin = require("copy-webpack-plugin");const common = require("./webpack.common");module.exports = merge(common, {  mode: "production",  plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(["public"])],});

执行打包命令

yarn webpack --config webpack.dev.js

DefinePlugin

DefinePlugin 容许在 编译时 将你代码中的变量替换为其余值或表达式。这在须要依据开发模式与生产模式进行不同的操作时,十分有用。例如替换域名

    new webpack.DefinePlugin({      // 值要求的是一个代码片段      API_BASE_URL: JSON.stringify("https://api.example.com"),    }),

留神,因为本插件会间接替换文本,因而提供的值必须在字符串自身中再蕴含一个理论的引号。通常,能够应用相似 '"production"' 这样的替换引号,或者间接用 JSON.stringify('production')

Tree Shaking

用于形容移除 JavaScript 上下文中的未援用代码。production 模式下曾经默认启用。

const path = require("path");const webpack = require("webpack");const { CleanWebpackPlugin } = require("clean-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = {  mode: "none",  entry: "./src/main.js",  output: {    filename: "bundle.js",    path: path.join(__dirname, "dist"),  },  optimization: {    // 模块只导出被应用的成员    usedExports: true,    // 压缩输入后果    minimize: true,  },};

sideEffects

通过 package.json"sideEffects" 属性作为标记,向 compiler 提供提醒,表明我的项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此能够平安地删除文件中未应用的局部。

{  "sideEffects": false}

如果你的代码的确有一些副作用,能够改为提供一个数组:

{  "sideEffects": ["./src/some-side-effectful-file.js"]}
"side effect(副作用)" 的定义是,在导入时会执行非凡行为的代码,而不是仅仅裸露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。

代码分包

多入口打包

实用于多页面打包,一个页面对应一个打包入口,提取公共局部

const { CleanWebpackPlugin } = require("clean-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = {  mode: "none",  entry: {    index: "./src/index.js",    album: "./src/album.js",  },  output: {    filename: "[name].bundle.js",  },  optimization: {    splitChunks: {      // 主动提取所有公共模块到独自 bundle      chunks: "all",    },  },  module: {    rules: [      {        test: /\.css$/,        use: ["style-loader", "css-loader"],      },    ],  },  plugins: [    new CleanWebpackPlugin(),    new HtmlWebpackPlugin({      title: "Multi Entry",      template: "./src/index.html",      filename: "index.html",      chunks: ["index"],    }),    new HtmlWebpackPlugin({      title: "Multi Entry",      template: "./src/album.html",      filename: "album.html",      chunks: ["album"],    }),  ],};

动静导入

动静导入的模块会被主动分包,实现按需加载

魔法正文:给分包的 bundle 定义名称

// 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: 'components' */ "./posts/posts").then(      ({ default: posts }) => {        mainElement.appendChild(posts());      }    );  } else if (hash === "#album") {    // mainElement.appendChild(album())    import(/* webpackChunkName: 'components' */ "./album/album").then(      ({ default: album }) => {        mainElement.appendChild(album());      }    );  }};render();window.addEventListener("hashchange", render);

CSS 文件压缩分包

yarn add terser-webpack-plugin optimize-css-assets-webpack-plugin mini-css-extract-plugin --dev
  • terser-webpack-plugin: 该插件应用 terser 来压缩 JavaScript
  • optimize-css-assets-webpack-plugin: 应用 cssnano 优化和压缩 CSS
  • mini-css-extract-plugin: 本插件会将 CSS 提取到独自的文件中,为每个蕴含 CSS 的 JS 文件创建一个 CSS 文件,并且反对 CSS 和 SourceMaps 的按需加载
const { CleanWebpackPlugin } = require("clean-webpack-plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const MiniCssExtractPlugin = require("mini-css-extract-plugin");const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");const TerserWebpackPlugin = require("terser-webpack-plugin");module.exports = {  mode: "none",  entry: {    main: "./src/index.js",  },  output: {    filename: "[name].bundle.js",  },  optimization: {    minimizer: [      // 申明数组会让webpack认为咱们须要自定义压缩,所以须要本人申明js文件的压缩      new TerserWebpackPlugin(),      new OptimizeCssAssetsWebpackPlugin(),    ],  },  module: {    rules: [      {        test: /\.css$/,        use: [          // 'style-loader', // 将款式通过 style 标签注入          MiniCssExtractPlugin.loader,          "css-loader",        ],      },    ],  },  plugins: [    new CleanWebpackPlugin(),    new HtmlWebpackPlugin({      title: "Dynamic import",      template: "./src/index.html",      filename: "index.html",    }),    new MiniCssExtractPlugin(),  ],};

Hash 文件名

依赖文件名 hash 缓存。

我的项目级,我的项目任何改变都会变

  output: {    filename: "[name]-[hash].js",  },

chunk 级,同一 chunk 改变会援用同 chunk 的变动

  output: {    filename: "[name]-[chunkhash].js",  },

文件级,不同的文件不同的 hash 值,且 hash 长度为 8 位

  output: {    filename: "[name]-[contenthash:8].js",  },