Webpack 思维导图

1,499 阅读5分钟

Webpack的学习

跟着官网手册进行练习。

单文件入口 (案例1)

webpack.config.js

const path = require('path');

module.exports = {
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  }
};

多文件入口 (案例2)

const path = require('path');

module.exports = {
  mode: 'production',
  entry: {
    main: './index.js',
    app: './app.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
};

Loaders (案例3)

webpack只能识别js和json文件,当我们模块中引入其他文件是我们需要使用loaders来处理这些文件

const path = require('path');

module.exports = {
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.png|jpg|gif|svg$/,
        use: ['file-loader']
      }
    ]
  }
};

在这里我们引入处理css和图片文件的loader,loader在最顶层有两个属性:

  1. test: 识别文件的正则表达式
  2. use: 处理该文件中类型文件的loader

插件 (案例4)

loaders是为了处理其他文件,而插件可以处理更大范围的任务,如打包优化,资源管理环境变量的注入等。

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js'
  },
  plugins: [
    new htmlWebpackPlugin({
      template: './index.html'
    })
  ]
};

在这个案例中,我们使用了html-webpack-plugin插件,指定了模板路径,打包时自动产出了index.html同时将产出的js文件引入到了index.html中。

可以分别为对各页面导出js

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: {
    main: './index.js',
    app: './app.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  plugins: [
    new htmlWebpackPlugin({
      filename: 'index.html',
      template: './index.html',
      chunks: ['main']
    }),
    new htmlWebpackPlugin({
      filename: 'main.html',
      template: './main.html',
      chunks: ['app']
    }),
  ]
};

htmlWebpackPlugin的chunks是enter中chunk的名称。

开发模式 (案例5)

设置mode属性为development。

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
    publicPath: '/'
  },
   plugins: [
      new htmlWebpackPlugin({
        template: "index.html"
      })
    ]
};

使用 source maps

当我们将多个文件打包成一个文件时,如果一个文件中出现错误,堆栈跟踪就会直接指向到打包文件,我们无法知道具体是那个文件出错。 这时如果我们使用source maps,source map 就会明确的告诉你那个文件出错。

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: './index.js',
  devtool: 'inline-source-map',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
    publicPath: '/'
  },
   plugins: [
      new htmlWebpackPlugin({
        template: "index.html"
      })
    ]
};

在 err.js中

console.lg()

浏览器报错

Uncaught TypeError: console.lg is not a function
    at Object../err.js (err.js:1)

使用webpack-dev-server

webpack-dev-server 为提供了一个简单的 web服务器,并且具有实时重新加载功能。

npm install --S webpack-dev-server
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
    publicPath: '/'
  },
  devServer: {
    contentBase: './dist',
    port: 8080
  },
  plugins: [
    new htmlWebpackPlugin({
      template: "index.html"
    })
  ]
};
{
  "name": "demo01",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --config webpack.config.js"
  },
  "author": "",
  "license": "ISC"
}

分离代码 (案例6)

当两个模块引用了同一个模块时,我们期望的是将这个模块单独打包,而不是分别打包到两个模块中造成代码重复。

webpack.config.js

const path = require('path');

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: {
    main: './index.js',
    another: './another-module.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/'
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

打包结果 index.jsanother-module.js 模块中引用的 lodash 被单独打包成 vendors~another~main.js;

Built at: 2019-06-18 14:54:31
                  Asset      Size                Chunks             Chunk Names
             another.js  15.2 KiB               another  [emitted]  another
                main.js  15.2 KiB                  main  [emitted]  main
vendors~another~main.js  1.36 MiB  vendors~another~main  [emitted]  vendors~another~main

缓存 (案例7)

如果每次运行后的文件名不更改,浏览器会认为它没更新,会使用缓存版本。

这时可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的名称。

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: {
    main: './index.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    publicPath: '/'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'cache',
      template: "index.html"
    })
  ]
};

提取提取引导模板和第三方库

将webpack的runtime代码和第三方库代码单独提取出来,因为这些代码不常修改,在修改源代码从新编译是webpack产出文件时不会修改这些文件名,浏览器会直接提取缓存文件。

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

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: {
    main: './index.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    publicPath: '/'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'cache',
      template: "index.html"
    })
  ],
  optimization: {
    // 提取runtime文件
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        // 提取第三方库代码
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

更详细的文档

注意: 在文档中提出要添加模块标识符才能达到修改本地文件,只变化main文件名,不变化其他打包文件的目的,但在我的测试下不需要加这个标识符自动有这种效果。

HMR (案例8)

热模块替换就是值只替换修改的模块,不刷新页面,提高开发效率。

webpack.config.js

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { HotModuleReplacementPlugin } = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: {
    main: './index.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[hash].js',
    publicPath: '/'
  },
  devServer: {
    contentBase: './dist',
    port: 8000,
    // 开启热替换
    hot: true
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'cache',
      template: "index.html"
    }),
    // 开启热替换
    new HotModuleReplacementPlugin()
  ]
};

index.js

import _ from 'lodash';
import printMe from './print'

function component() {
  var element = document.createElement('div');
  var btn = document.createElement('button');

  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = printMe;

  element.appendChild(btn);

  return element;
}

document.body.appendChild(component());

if (module.hot) {
  module.hot.accept('./print.js', () => {
    console.log('accept the updated printMe module!');
    printMe();
  })
}

当我们更新print.js时,只会更新该模块。

懒加载 (案例9)

index.js

import _ from 'lodash';

function component() {
  var element = document.createElement('div');
  var btn = document.createElement('button');

  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  btn.innerHTML = 'Click me and check the console!';
  btn.onclick = e => import('./print').then(module => {
    const print = module.default();
    print();
  });

  element.appendChild(btn);

  return element;
}

document.body.appendChild(component());

if (module.hot) {
  module.hot.accept('./print.js', () => {
    console.log('accept the updated printMe module!');
    printMe();
  })
}

采用import()前:

Built at: 2019-06-18 20:01:48
                       Asset       Size  Chunks             Chunk Names
                  index.html  196 bytes          [emitted]
main.3da867b5364b7a8d8c20.js   2.28 MiB    main  [emitted]  main


采用import()后:

Built at: 2019-06-18 20:06:49
                       Asset       Size  Chunks             Chunk Names
   0.fc6d77991376136f948f.js  940 bytes       0  [emitted]
                  index.html  196 bytes          [emitted]
main.fc6d77991376136f948f.js   2.29 MiB    main  [emitted]  main

利用懒加载,webpack会将该模块单独生成一个文件,当我们点击按钮时,浏览器才会加载该模块文件,这样减小了页面初始化时间。

shim 预制依赖 (案例10)

设置全局全量

象jQuery这种第三方库需要引用第三方库,我们可以设置全局变量。

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ProvidePlugin } = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map',
  entry: {
    main: './index.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[hash].js',
    publicPath: '/'
  },
  devServer: {
    contentBase: './dist',
    port: 8000
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'cache',
      template: "index.html"
    }),
    new ProvidePlugin({
      _: 'lodash'
    })
  ]
};

这样我们可以在任何地方使用 _ 方法。 我们还可以通过配置 数组路径来暴露某个模块的单个导出: FunctionName: [module, child]

   new ProvidePlugin({
      _: 'lodash',
      lodash: ['lodash', 'join']
   })

在这个例子中我们可以全局调用lodash(即lodash模块的join方法)方法了。

tree shaking (案例11)

optimization.usedExports: true, 不导出未引用到的代码,在production模式下默认开启,其他模式默认关闭。

一个完整简单的webpack配置 (案例12)