webpack 4 的从零开始

最近的项目开始了优化的工作,趁此机会重新学习了一波 webpack 4,希望把部分学习过程分享给大家。

webpack 是什么?

工欲善其事,必先利其器。了解工具是很重要的,但在开始之前,我们要问自己一件很重要的问题:“该工具解决了什么问题?” webpack 是一个 模块打包器,它可以合并一组模块和他们的依赖关系,输出一个或者多个文件。除了模块打包以外,webpack 还可以分析你的项目结构,找到 JavaScript 模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypewScript等),并将其打包为合适的格式以供浏览器使用。

开发前的准备

既然是从零开始,那我们就新建项目来学习 webpack 的使用。

mkdir webpack4-demo && cd webpack4-demo
npm init -y
npm install webpack webpack-cli --save-dev
复制代码
  • webpack 4 分离了 webpack-cli 与 webpack 所以需要单独安装 cli

初始化之后我们对 package.json 做如下修改:

"scripts": {
  "build": "webpack"
}
复制代码

这样,在运行 npm run build 时,会使用 node_modules 中的 webpack 命令。

基本概念

从 webpack 4 开始可以不需要任何配置(其实就是自带了一些默认配置)。但随着项目的发展我们还是需要对配置进行修改和优化的。webpack 会读取项目根目录下 webpack.config.js 文件。

  • entry 入口:webpack 架构第一步会从入口文件开始,可以是一个或者多个。
module.exports = {
  entry: {
    first: './src/first.js',
    second: './src/second.js',
  }
};
复制代码
  • output 输出:默认情况下只输出一个文件,通过配置可以解决这个问题。
const path = require('path'); 
 
module.exports = {
  entry: {
    first: './src/one.js',
    second: './src/two.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}
复制代码

Loader

Loader 非常强大,可以将 scss、less、typescript 等浏览器无法直接理解的转换为浏览器可以理解的语言,甚至连图片字体等资源也可以交由 loader 来处理。而使用它们非常简单,只需三步:

  1. 安装 loader
  2. webpage.config.js 中指定规则
  3. 生效!

解析 css

首先安装 css 相关的 loader

npm install css-loader style-loader
复制代码

修改 webpage.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
}
复制代码

index.js 中引入 css

import './style.css';
复制代码

webpack 将会做如下工作:

  1. Webpack 将尝试解析style.css文件
  2. 文件名匹配到 /\.css$/
  3. 该文件将由 css-loader 解释
  4. css-loader 的结果将传递给 style-loader
  5. 最后,style-loader 将返回一个 JavaScript 代码

解析 scss

同样,按安装 scss 的 loader

npm install node-sass sass-loader
复制代码
  • tips: sass-loader需要安装node-sass才能工作。

修改 webpage.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  },
}
复制代码

解析图片

安装相关 loader

npm install url-loader file-loader
复制代码

tips: url-loader 是对 file-loader 的上层封装

修改 webpage.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader','sass-loader']
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 5000
            }
          }
        ]
      }
    ]
  }
};
复制代码

url-loader 可以将小图片转换为 base64 URI,这样可以减少网络请求,而大小由 limit 字段决定。

转换前

body {
  background-image: url('./big-background.png');
}
.icon {
  background-image: url('./icon.png');
}
复制代码

转换后

<style type="text/css">
body {
  background-image: url(ca3ebe0891c7823ff1e137d8eb5b4609.png); 
}
.icon {
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAALElEQVR4AWMYIWAU1FPLoP9AXEFI0QEi8H+YYdQyqIEaXuumRhh1DZdUMwoATlYWfwh9eYkAAAAASUVORK5CYII=);
 }
</style>
复制代码

解析 js

使用 babel-loader 后,就可以愉快的在 javascript 中写 es6 es7.... babel 可以通通帮我们翻译成浏览器可以认识的旧版本。

按照国际惯例,先安装 loader

npm install babel-loader babel-core babel-preset-env
复制代码

为什么安装这么多?别着急,一个一个来解释。

  1. babel-core babel 编译库的核心包。
  2. babel-loader 配合 webpack 的 loader。
  3. babel-preset-env 在 babel 翻译之前,我们需要先告诉 babel 我们要以什么样的规范去编译。比如按照 es6 标准编译,那么我们就安装一个 babel-preset-es2015, 同样,如果我们要按照 es7 来编译,那么我们就安装 babel-preset-es2016,而 es 新规范层出不穷,如果要按照最新的规范做编译,直接安装 babel-preset-env 就可以了,它包含了 babel-preset-es2015, babel-preset-es2016, babel-preset-es2017,等价于 babel-preset-latest,可以编译所有最新规范中的代码。

之后就是配置规则啦:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};
复制代码

tips: exclude 表示不会转换那些文件。

插件

webpack 有着丰富的插件接口,这个插件接口使 webpack 变得极其灵活。同样,使用插件也分三步。

  1. 安装插件
  2. 通过 new 实例化插件,这样可以多次使用同一个插件。
  3. 生效!

HtmlWebpackPlugin

应该是最常用的插件之一,它可以帮我们在打包后自动生成引入打包文件的 HTML,对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。可以使用已有的模板来生成。

安装插件

npm install --save-dev html-webpack-plugin
复制代码

添加配置

const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
    plugins: [
        new HtmlWebpackPlugin({template: './src/index.html'})
    ]
};
复制代码

如果输出的是多个文件呢?也很简单

const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
    entry: {
        one: './src/one.js',
        two: './src/two.js',
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'one.html',
            template: './src/one.html',
            chunks: ['one']
        }),
        new HtmlWebpackPlugin({
            filename: 'two.html',
            template: './src/two.html',
            chunks: ['two']
        })
    ]
};
复制代码

MiniCssExtractPlugin

MiniCssExtractPlugin 可以帮我们提取 css 到单独的文件中,在 webapck 4 之前的版本是用 ExtractTextWebpackPlugin 来实现的,webapck 4 之后才可以使用 MiniCssExtractPlugin。而且与 ExtractTextWebpackPlugin 比起来有如下特点

  1. 异步加载
  2. 没有重复的编译
  3. 更容易使用
  4. 特定于CSS 但是目前还不支持开发时的 HRM (Hot Module Replacement),所以需要针对开发版单独配置。

安装插件

npm install --save-dev mini-css-extract-plugin
复制代码

配置规则

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== 'production'

module.exports = {
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      }
    ]
  },
 plugins: [
    new MiniCssExtractPlugin({
      filename: devMode ? '[name].css' : '[name].[hash].css',
      chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
    })
  ],
}
复制代码

代码拆分

在解释这个概念之前,我们先看如下代码: user.js

export default [
  { name: "xiaoming", age: 28 },
  { name: "xiaohong", age: 24 },
  { name: "zhangsan",  age: 31 },
  { name: "lisi", age: 40 }
]
复制代码

a.js

import _ from 'lodash';
import users from './users';
 
const zhangsan = _.find(users, { name: 'zhangsan' });
复制代码

b.js

import _ from 'lodash';
import users from './users';
 
const lisi = _.find(users, { name: 'lisi' });
复制代码

webpack.config.js

module.exports = {
  entry: {
    a: "./src/a.js",
    b: "./src/b.js"
  },
  output: {
    filename: "[name].[chunkhash].bundle.js",
    path: __dirname + "/dist"
  }
};
复制代码

运行 webpack 后,我们会看到两个文件 a.[chunkhash].bundle.jsb.[chunkhash].bundle.js ,打开这两个文件后我们发现:

居然每一个文件都有 lodash ..... 所以我们希望可以共享的代码抽离出来,其他文件引入这个文件即可。帮我们实现这个功能的就是 SplitChunksPlugin。

SplitChunksPlugin

从 webpack 4 开始,就删除了 CommonsChunkPlugin,而是引入了 SplitChunksPlugin ,而且开箱即用,不需要另外安装。下面我们修改一下 webpack 的默认值。

module.exports = {
  entry: {
    a: "./src/a.js",
    b: "./src/b.js"
  },
  output: {
    filename: "[name].[chunkhash].bundle.js",
    path: __dirname + "/dist"
  },
  optimization: {
    splitChunks: {
      chunks: "all"
    }
  },
};
复制代码

修改后重新运行,我们再次打开文件查看结果:

可以发现,新打包了一个 vendors~a~b~.[hashchunk].js,这个文件是 a.js 和 b.js 中共用部分的代码, 同时,文件 a 和 b 中都不再有 lodash 了。 但是这里还有个小问题,就是文件 user.js 中输出的数据其实也是可以共用的,而这一部分没有被单独被打包到一个新文件的原因是 默认情况下只打包大于 30k 的文件。同样我们通过修改配置可以实现拆分共用文件的部分。

module.exports = {
  entry: {
    a: "./src/a.js",
    b: "./src/b.js"
  },
  output: {
    filename: "[name].[chunkhash].bundle.js",
    path: __dirname + "/dist"
  },
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 0
    }
  }
};
复制代码

运行结果:

可以发现新打包了一个 a~b.[chunkhash].js 的文件,这就是共用的数据。然而事实上,默认不打包 30k 以下的文件是很合适的配置。因为多一个文件意味着多一个请求,在实际开发中,我们要在文件大小和请求中间做一个平衡。

猜你喜欢

转载自blog.csdn.net/weixin_34293246/article/details/91390886