webpack进阶1

675 阅读5分钟

webpack中的文件监听

文件监听是在发现源码发生变化时,自动重新构建出新的输出文件。不用手动重新构建和刷新浏览器。

webpack 开启监听模式,有两种方式:

  • 启动webpack 命令时,带上--watch 参数;
  • 在配置webpack.config.js 中设置watch: true。

文件监听的原理分析

轮询判断文件的最后编辑时间是否变化。 某个文件发生了了变化,并不会立刻告诉监听者,⽽而是先缓存起来,等aggregateTimeout。

module.export = {
  //默认false,也就是不不开启
  watch: true,
  //只有开启监听模式时,watchOptions才有意义
  wathcOptions: {
    //默认为空,不监听的文件或者文件夹,支持正则匹配
    ignored: /node_modules/,
    //监听到变化发生后会等300ms再去执行,默认300ms
    aggregateTimeout: 300,
    //判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次
    poll: 1000,
  },
};

缺陷:每次需要手动刷新浏览器。

热更新:webpack-dev-server

WDS 不需要刷新浏览器,不输出文件,而是放在内存中。使用webpack自带的HotModuleReplacementPlugin插件,需要安装webpack-dev-server。

yarn add webpack-dev-server --dev

webpack.config.js中热更新的配置,:

// 使用HashedModuleIdsPlugin插件
plugins: [new webpack.HashedModuleIdsPlugin()],
devServer: {
    // 监听的目录
    contentBase: "./dist",
    // 告诉DevServer 要开启模块热替换模式
    hot: true,
},
// 只能配置在开发模式下
mode: "development",

package.json中的快捷配置,其中--open参数表示开启热更新后自动打开浏览器显示内容: 配置完后可以直接使用快捷命令:

yarn dev

热更新的原理分析

  1. Webpack Compile依据webpack.config.js的配置将初始的文件编译打包,生成编译好的文件,例如bundle.js;
  2. webpack同时生成 HMR Server 和 Bundle server两个服务,同时在bundle.js中注入HMR Rumtime。
    Bundle server:提供文件在浏览器的访问。 
    HMR Server:将热更新的文件输出给HMR Rumtime。  
    HMR Rumtime:更新文件的变化。
    
  3. 如果本地的文件发生了变化,Webpack再次打包,并发送给HMR Server,HMR Server知道那些资源发生了改变,就以websocket将JSON数据通知客户端的HMR Rumtime哪些文件发生了变化。
  4. HMR Rumtime就会在内存中进行增量更新。

热更新分两个阶段:

  • 启动阶段还是依赖磁盘文件去编译(如图:1 -> 2 -> A -> B);
  • 更新阶段是直接内存增量更新的(如图:1 -> 2 -> 3 -> 4)。

文件指纹

就是打包后输出的文件名的后缀

不同种类的文件指纹

  1. Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会更改;
  2. Chunkhash:和webpack打包的chunk 有关,不同的entry 会生成不同的chunkhash值;
  3. Contenthash:根据文件内容来定义hash ,文件内容不变,则contenthash不变。

一般使用chunkhash来设置js文件指纹,使用contenthash来设置css文件指纹,使用hash来设置图片等文件的指纹。

设置file-loader 的name的占位符:

需要安装mini-css-extract-plugin插件,并且使用MiniCssExtractPlugin.loader取代“style-loader”。

"use strict";
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: { index: "./src/index.js", search: "./src/search.js" },
  output: {
    path: path.join(__dirname, "dist"),
    filename: "[name][chunkhash].js",
  },
  module: {
    rules: [
      { test: /\.js$/, use: "babel-loader" },
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
      },
      {
        test: /\.(jpg|png|gif|jpeg)$/,
        use: [
          {
            loader: "url-loader",
            options: { limit: 10240, name: "[name]_[hash:8].[ext]" },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name][contenthash:8].css",
    }),
  ],
  mode: "production",
};

代码压缩

JS 文件的压缩

在webpack 4 里面内置了uglifyjs-webpack-plugin插件用于js代码的压缩

css 文件的压缩

先安装optimize-css-assets-webpack-plugin和cssnano。

yarn add optimize-css-assets-webpack-plugin cssnano --dev

// 在plugins使用OptimizeCSSAssetsPlugin插件就可以。
new OptimizeCSSAssetsPlugin({
  assetNameRegExp: /\.css$/g,
  cssProcessor: require("cssnano"),
}),

html 文件的压缩

修改html-webpack-plugin,设置压缩参数,先安装:

yarn add html-webpack-plugin --dev

// 在plugins使用HtmlWebpackPlugin插件
new HtmlWebpackPlugin({
  // 指定html模板
  template: path.join(__dirname, "src/search.html"),
  filename: "search.html",
  // 指定引入的的search文件
  chunks: ["search"],
  // inject为true,js和css会自动注入到html中
  inject: true,
  minify: {
    html5: true,
    collapseWhitespace: true,
    preserveLineBreaks: false,
    minifyCSS: true,
    minifyJS: true,
    removeComments: false,
  },
}),

参数:

  • title:生成页面的titile元素;
  • filename:生成的html文件的文件名。默认index.html,可以直接配置带有子目录;
  • template:模版文件路径;
  • inject:插入的script插入的位置,四个可选值:
    • true: 默认值,script标签位于html文件的body底部;
    • body: 同true;
    • head: script标签位于html文件的head标签内;
    • false: 不插入生成的js文件,只是生成的html文件。
  • minify:对html文件进行压缩。属性值是false或者压缩选项值。默认false不对html文件进行压缩。
  • hash:给生成的js文件尾部添加一个hash值。这个hash值是本次webpack编译的hash值。默认false;
  • chunks:在html文件中引用哪些js文件,用于多入口文件时。不指定chunks时,所有文件都引用。

自动清理构建目录

可以通过npm scripts 清理构建目录,即在package.json文件"scripts"中配置

"build": "rm -rf ./dist && webpack --config webpack.prod.js",

但是这种方式不够优雅,所以可以使用clean-webpack-plugin插件,它默认会删除output 指定的输出目录。

安装clean-webpack-plugin插件:

yarn add clean-webpack-plugin --dev

在webpack.config.js里引入模块

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

在plugins里添加插件:

plugins: [
    new CleanWebpackPlugin(),
};

自动补齐CSS3 前缀

CSS3 的属性为什么需要前缀?

因为现在移动端和pc端浏览器种类众多。

.box {
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    -o-border-radius: 10px;
    border-radius: 10px;
}

webpack使用postcss-loader和autoprefixer插件自动补齐CSS3 前缀

首先安装以上两者:

yarn add postcss-loader autoprefixer --dev

在css、less、sass文件里面使用他们


{
test: /\.less$/,
use: [
  MiniCssExtractPlugin.loader,
  "css-loader",
  "less-loader",
  {
    loader: "postcss-loader",
    options: {
      plugins: () => [
        require("autoprefixer")({
          //适应浏览器前两个版本并且用大于1%的用户,使用ios 7的移动端系统浏览器
          browsers: ["last 2 version", ">1%", "ios 7"],
        }),
      ],
    },
  },
],
},

响应式布局

基于rem实现响应式布局,rem是基于html的font-size的值来设定的,假如font-size为16px,那么1rem=16px,所以rem 是相对单位。

使用px2rem-loader将px自动转换成rem。可以配合使用手淘的lib-flexible库,页面渲染时计算根元素的font-size值。

安装px2rem-loader:

yarn add postcss-loader autoprefixer --dev

安装lib-flexible:

yarn add lib-flexible

webpack配置:

{
test: /\.less$/,
use: [
  MiniCssExtractPlugin.loader,
  "css-loader",
  "less-loader",
  // 配置px2rem-loader
  {
    loader: "px2rem-loader",
    options: {
      // 设置rem和px的转换比率
      remUnit: 14,
      // 设置rem转换后保留的小数点位数
      remPrecision: 8,
    },
  },
],
},

构建后的效果

webpack实现资源内联

资源内联可以加载页面框架的初始化脚本,css 内联避免页面闪动,减少HTTP网络请求数。

HTML和JS内联

安装raw-loader:

yarn add raw-loader --dev

内联设置:

<!DOCTYPE html>
<html lang="en">
  <head>
    <%=require('raw-loader!./inline.html').default%>
    <title>index</title>
    <script>
      <%= require('raw-loader!babel-loader!./inline.js').default %>
    </script>
  </head>
  <body></body>
</html>

如果按装的是raw-loader@0.5.1版本,就不用加".default"代码。

CSS内联

使用style-loader。配置:

{
    loader: 'style-loader',
    options: {
    insertAt: 'top', // 样式插入到<head>
    singleton: true, //将所有的style标签合并成一个
    }
}

多页面打包

使用glob库动态获取entry 和设置html-webpack-plugin 数量。

先安装glob库

yarn add glob --dev

webpack配置代码:

const glob = require("glob");
const setMPA = () => {
  const entry = {};
  const HtmlWebpackPlugins = [];
  const entryFiles = glob.sync(path.join(__dirname, "./src/*/*.js"));

  entryFiles.forEach((entryFile) => {
    const match = entryFile.match(/src\/(.*)\/(index|search).js/);
    const pageName = match && match[1];

    pageName && (entry[pageName] = entryFile);
    pageName &&
      HtmlWebpackPlugins.push(
        new HtmlWebpackPlugin({
          template: path.join(__dirname, `src/${pageName}/${pageName}.html`),
          filename: `${pageName}.html`,
          chunks: [pageName],
          inject: true,
          minify: {
            html5: true,
            collapseWhitespace: true,
            preserveLineBreaks: false,
            minifyCSS: true,
            minifyJS: true,
            removeComments: false,
          },
        })
      );
  });

  return {
    entry,
    HtmlWebpackPlugins,
  };
};

const { entry, HtmlWebpackPlugins } = setMPA();

module.exports = {
  entry: entry,
  output: {
    path: path.join(__dirname, "dist"),
    filename: "[name][chunkhash].js",
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name][contenthash:8].css",
    }),
    new OptimizeCSSAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require("cssnano"),
    }),
  ].concat(HtmlWebpackPlugins),
  mode: "production",
};

使用source map

简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。

有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。线上排查问题的时候可以将sourcemap 上传到错误监控系统。

source map 关键字

  • eval: 使用eval包裹模块代码;
  • source map: 产生.map文件;
  • cheap: 不包含列信息;
  • inline: 将.map作为DataURI嵌入,不单独生成.map文件;
  • module:包含loader的sourcemap。

source map 类型

webpack配置很简单:

devtool: "source-map",

基础库分离

对一些公共的文件进行分离,例如将react、react-dom 基础包通过cdn 引入,不打入bundle 中。

使用html-webpack-externals-plugin

安装:

yarn add html-webpack-externals-plugin --dev

配置:

const HtmlWebpackExternalsPlugin = require("html-webpack-externals-plugin");

new HtmlWebpackExternalsPlugin({
  externals: [
    {
      module: "react",
      entry: "https://11.url.cn/now/lib/16.2.0/react.min.js",
      global: "React",
    },
    {
      module: "react-dom",
      entry: "https://11.url.cn/now/lib/16.2.0/react-dom.min.js",
      global: "ReactDOM",
    },
  ],
}),

受用内置的splitChunks

optimization: {
  splitChunks: {
    minSize: 0,
    cacheGroups: {
      commons: {
        test: /(react|react-dom)/,
        name: "commons",
        chunks: "all",
        minChunks: 1,
      },
      hello: {
      	test: /(hello)/,
        name: "hello",
        chunks: "all",
        minChunks: 1,
      },
    },
  },
},

// 在HtmlWebpackPlugin将生成的js文件引入
chunks: ["hello", "commons", pageName],

chunks参数:

  • async 异步引⼊入的库进⾏行行分离(默认);
  • initial 同步引⼊入的库进⾏行行分离;
  • all 所有引⼊入的库进⾏行行分离(推荐)。

name:分离的包的名字。
test: 匹配出需要分离的包。
minChunks: 设置最小引用次数为2次。
minuSize: 分离的包体积的大小。