跳到主要内容

WebPack 学习

参考资料

概述

什么是 webpack

webpack 是一个前端项目构建工具(打包工具),提供 代码压缩混淆、处理 js 兼容性问题、性能优化 等功能

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

前端模块化开发

传统开发模式的主要问题

  1. 命名冲突
  2. 文件依赖

而引入模块化的目的就是为了解决这个问题,就是将单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块;在 ES6 出来之前 JavaScript 社区有三种常见的模块化规范 AMD、CMD、CommonJS

但是,这些社区提出的模块化标准,还是存在一定的差异性与局限性、并不是浏览器和服务器通用的模块化标准,例如:

  • AMD 和 CMD 适用于浏览器端的 Javascript 模块化
  • CommonJS 只适用于服务器端的 Javascript 模块化

所以,ES6 语法规范中,在语言层面上定义了模块化规范,是浏览器与服务器端通用的模块化开发规划

例如 ES6 模块化规范中定义:

  • 每个 js 文件都是一个单独的模块
  • 导入模块成员使用 import 关键字
  • 暴露模块成员使用 export 关键字

webpack 基础使用

配置环境

先安装 webpack

# --save-dev 可以直接使用 -D 参数
# --save 可以直接使用 -S 参数
npm install -–save-dev webpack webpack-cli

添加配置文件

在根目录创建一个 webpack.config.js 的文件

参考资料 webpack 中文文档

webpack.config.js 配置文件各项参数

  • entry: 入口文件,指定WebPack 用哪个文件作为项目的入口
  • output:输出,指定WebPack把处理完成的文件放置到指定路径
  • module:模块,用于处理各种类型的文件
  • plugins:插件,如热更新、代码重用等
  • resolve:设置路径指向
  • watch:监听,用于设置文件改动后直接打包
  • mode:指定构建模式,development 表示开发模式,还有一个 production 表示正式环境,区别就是 development 不会压缩混淆代码

入口 (entry)

入口会指示 webpack 应该使用哪个文件来作为构建其内部依赖图的开始。

进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。在 webpack 中入口有多种方式来定义,如下面例子:

单个入口(简写)语法:

module.exports = {
entry: "./src/main.js"
}

输出 (output)

output 属性会告诉 webpack 在哪里输出它创建的 bundles ,以及如何命名这些文件,默认值为 ./dist

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

加载器 (loader)

loader 让 webpack 可以去处理那些非 JavaScript 文件( webpack 自身只理解 JavaScript )。loader 可以将所有类型的文件转换为 webpack 能够有效处理的模块,例如,开发的时候使用 ES6 ,通过 loader 将 ES6 的语法转为 ES5 ,如下配置:

const path = require('path');

module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
// 使用
test: /\.js$/,
// exclude 为排除项,表示 babel-loader 不需要处理 node_modules 中的 js 文件
exclude: /node_modules/,
// 表示使用 'babel-loader' 加载器来处理 .js 文件;(loader: "babel-loader" 这种已经废弃了)
use: 'babel-loader',
options: [
presets: ["env"]
]
}
]
}
}

下面会专门讲这个

插件 (plugins)

参考文档 HtmlWebpackPlugin

loader 被用于转换某些类型的模块,而插件则可以做更多的事情。包括打包优化、压缩、定义环境变量等等。插件的功能强大,是 webpack 扩展非常重要的利器,可以用来处理各种各样的任务。使用一个插件也非常容易,只需要 require() ,然后添加到 plugins 数组中。

下面以这个 html-webpack-plugin 为例;html-webpack-plugin 插件是用于编译 Webpack 项目中的 html 类型的文件,如果直接将 html 文件置于 ./src 目录中,用 Webpack 打包时是不会编译到生产环境中的。因为 Webpack 编译任何文件都需要基于配置文件先行配置的。

npm install --save-dev html-webpack-plugin
//webpack.config.js 
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
home: path.resolve(__dirname, './src/app.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
new htmlWebpackPlugin({
// template 指定你生成的文件所依赖哪一个 html 文件模板
template: './src/index.html',
filename: 'index.output.html'
})
]
}

添加快捷命令

在 package.json 配置文件中的 scripts 节点下新增 dev 脚本

"scripts": {
"dev": "webpack" // script 节点下的脚本可以通过 npm run 执行
},

执行命令启动

npm run dev

可以看到输出如下,表示把 ./src/index.js 打包成 main.js 文件了

> webpack

Hash: 76fcf8b80028b8893c91
Version: webpack 4.44.1
Time: 328ms
Built at: 2020-08-03 23:55:00
Asset Size Chunks Chunk Names
main.js 322 KiB main [emitted] main
Entrypoint main = main.js
[./src/index.js] 166 bytes {main} [built]
+ 1 hidden module

配置自动打包工具

安装 webpack 的自动打包工具,使之能监听代码修改了自动打包

npm install webpack-dev-server -D

然后在 package.json 配置文件中的 scripts 节点下修改 dev 脚本

// --open 打包完成后自动打开浏览器界面
// --host 配置 IP 地址
// --port 配置端口
"scripts": {
// 在 webpack5 之后这个 webpack-dev-server 启动改成了这个
"dev": "webpack serve"
},

生成预览页面

安装生成预览页面的插件(就是访问项目根目录就能自动访问到 src/index.html 的信息)

原理就是复制一份指定路径的文件到内存里,访问项目根目录时,把这个内容返回出去

npm install html-webpack-plugin -D

然后修改 webpack.config.js 配置文件,添加如下配置信息

const HtmlWebpackPlugin = require('html-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 创建插件的实例对象
template: './src/index.html', // 指定要用到的模板文件
filename: 'index.html' // 指定生成的文件名称,该文件存在于内存中,在目录中不显示
})

再在 webpack.config.js 配置文件向外暴露配置对象

module.export = {
plugins: [htmlPlugin] // plugin 数组用于暴露 webpack 打包用到的一些插件
}

使用案例

const path = require('path');

module.exports = {
mode: "development", // "production" | "development"
// 选择 development 为开发模式, production 为生产模式
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
options: [
presets: ["env"]
]
}
]
},
plugins: [
...
]
}

实现一个隔行变色的效果

<ul>
<li>这是第1个li</li>
<li>这是第2个li</li>
<li>这是第3个li</li>
<li>这是第4个li</li>
<li>这是第5个li</li>
<li>这是第6个li</li>
</ul>
import $ from 'jquery'

$(function(){
// 奇数行
$('li:odd').css('backgroundColor','pink');
// 偶数行
$('li:even').css('backgroundColor','lightblue');
})

但是如果向上面那样直接使用 import 导入模块是执行不了的

npm run dev

使用 webpack 打包后更改成 打包后的 js 就行了

<script src="../dist/main.js"></script>

Webpack的加载器

在实际开发中,webpack 默认只能打包处理以 .js 后缀名结尾的模块,其他非 .js 后缀名结尾的模块,webpack 默认处理不了,需要调用 loader 加载器才可以正常打包,否则会报错

例如在 JS 里引入 css

import './css/temp.css'

就会报找不到 loader 的错

loader 加载器一般就是打包处理某个特定文件的模块 例如:

  • less-loader 可以打包 .less 相关的文件
  • sass-loader 可以打包处理 .scss 相关的文件
  • url-loader 可以打包处理 css 中与 url 路径相关的文件

a0f1aV.png

webpack 加载器基本使用

例如处理 css 文件的 leader

npm install style-loader css-loader -D # 安装处理 css 文件的 loader

然后在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

Loaders 可以通过传入多个 loaders 以达到链式调用的效果,它们会从右到左被应用(从最后到最先配置)

所以先执行 'css-loader' 再执行 'style-loader'

var path = require('path');

module.exports = {
mode: 'development',
entry: path.join(__dirname,'./src/index.js'), // 打包入口文件的路径
output: {
path: path.join(__dirname, './dist'),
filename: 'main.bundle.js' // 输出文件的名称
},
module: {
rules:[
{test: /\.css$/, use: ['style-loader', 'css-loader']}
]
}
};

打包样式表的图片和字体

当 css 文件里面引用了图片或字体文件时时就需要使用 url-loader file-loader 来处理,否则报错

npm install url-loader file-loader -D # 安装处理文件和 url 的 loader

然后在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

module: {
rules:[
{
test: /\.js|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
use: 'url-loader?limit=16940'
}
]
}

规则中的 ? 后面是 loader 的参数项 limit 用来指定图片的大小,单位是字节(byte),只有小于 limit 大小的图片才会被转成 base64 图片(就是直接把图片的16 进制码写在 css 样式里)

Vue 组件的加载器

npm install vue-loader vue-template-compiler -D

然后在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
module: {
rules:[
{test: /\.vue$/,loader: 'vue-loader'}
]
},
plugins: [
new VueLoaderPlugin()
]
};

整合 babel 模块

为什么不能单独用 babel

参考资料 关于webpack,babel,以及es6和commonJS之间的联系

babel 是将es6转换成es5,转换后的代码遵循 COMMONJS 规范,而这个规范,浏览器是不能识别的,直接运行会报错(例如 import这个关键词)

所以 为了将 babel 生成的 commonJS 规范的 es5 写法能够在浏览器上直接运行,就借住了 webpack 这个打包工具来完成,因为 webpack 本身也是遵循 commonJS 这个规范的

//module.exports是commonJS的接口输出规范,es6的规范是export
module.exports = {
entry: path.join(__dirname, 'index.js'),
output: {
path: path.join(__dirname, 'outs'),
filename: 'index.js'
},
};

基本配置

参考文档 babel-loader

npm install -–save-dev webpack webpack-cli

# 安装 babel 转换器相关的包
npm install -D babel-loader @babel/core @babel/preset-env webpack

然后在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则如下:

const path = require('path');

module.exports = {
entry: "./src/main.js", // 入口函数
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
// use 表示使用 'babel-loader' 加载器来处理 .js 文件;
// exclude 为排除项,表示 babel-loader 不需要处理 node_modules 中的 js 文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}

然后就可以在 package.json 里面加上这两个编译命令了

"dev": "webpack --mode development",
"build": "webpack --mode production"

如果 babel 安装了插件还是需要编写配置文件(babel.config.json)

如下

{
"presets": [
[
"@babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
}

References