前言
webpack是一个Javascript应用程序的静态模块打包器,接下来我们将通过理解以下四个核心概念:
- entry(入口)
- output(输出)
- loader(转换器)
- plugins(插件)
一起来使用webpack定制前端开发环境
安装和使用
安装
npm install webpack webpack-cli -g
使用
- 创建一个项目在
src
目录下新建index.html
,index.css
,index.js
以及assets
文件夹等一个基础的项目结构 - 在项目根目录下使用
npm init
创建package.json
文件 npm install webpack -D
npm install webpack-cli -D
分别安装webpack和webpack-cli- 在package.json中,添加一个npm scripts
1 2 3 4 5 6 7
"scripts": { "build": "webpack --mode production" }, "devDependencies": { "webpack": "^4.37.0", "webpack-cli": "^3.3.6", }
此时
npm run build
就会发现新增了一个dist
目录,里面存放着webpack构建的main.js文件 - 最后还需要一个
webpack.config.js
配置文件,配置实际项目中需要的功能,一起来配置吧~ - 关联HTML
html-webpack-plugin
为html文件引入外部资源如script、link等,可以生成创建html入口文件,比如单页面可以生成一个html文件入口,配置n个html-webpack-plugin可以生成n个页面入口 它是一个独立的npm package
,使用前先安装到项目的开发依赖中:
npm install html-webpack-plugin -D
然后在webpack.config.js中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cosnt HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({ // 打包输出html
title: 'webpack test app', // 生成的html文件的标题
minify: { // 压缩html文件
removeComments: true, //移除HTML中的注释
collapseWhitespace: true, // 删除空白符与换行符
miniFyCSS: true // 压缩内联css
},
filename: 'index.html', // 输出的html文件的名称
})
]
}
- 构建CSS
css-loader 负责解析CSS代码,主要是处理CSS中的依赖,例如@import
和url()
等引用外部文件的声明
style-loader 将css-loader解析的结果转变成JS代码,运行时动态插入style
标签来让CSS代码生效
此时CSS代码会转变成JS和index.js一起打包了,需要单独把CSS分离需要extract-text-webpack-plugin插件
上述基础上我们还会使用Less/Sass等CSS预处理器,请看下面例子:
预先安装css-loader、style-loder、less-loader:
1
npm install css-loader --save-dev
1
npm install style-loder --save-dev
1
npm install less-loader --save-dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader',
}),
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'less-loader'
],
}),
},
],
},
plugins: [
new ExtractTextPlugin('index.css'),
],
}
- 处理图片文件
file-laoder 用于处理很多类型的文件,主要作用是直接输出文件,把构建后的文件路径返回 预先安装file-loader
1
npm install css-loader --save-dev
index.js
1
import img from './img.png'
1
2
3
4
5
6
7
8
9
10
11
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {},
},
],
},
]
- 使用Babel
Babel 可以编译ES新特性的工具,配置Babel以便使用ES6,ES7标准来编写js代码
安装:1
npm install -D babel-loader @babel/core @babel/preset-env webpack
1 2 3 4 5 6 7 8 9 10 11 12 13
module: { rules: [ { test: /\.js$/, include:[ path.resolve(__dirname, 'src'), ], use: { loader: 'babel-loader' } } ] }
- 启动静态服务
以上步骤已经处理了多种的文件类型的webpack配置,接下来可以使用webpack-dev-server在本地开启服务来进行开发和调试。
安装:1
npm install webpack-dev-server --save-dev
在
package.json
添加启动命令:1 2 3 4
"scripts": { "build": "webpack --mode production", "start": "webpack-dev-server --mode development" }
当 mode 为 development 时,具备 hot reload 的功能,即当源码变化时,会更新当前页面 尝试运行
npm run start
然后就可以访问 http://localhost:8080 查看页面啦! 可以通过devServer
字段配置 webpack-dev-server,下面是几个常用配置:
sevServer.proxy:
访问单独的后端开发服务器APi,并且希望在同域名下发送API请求,可以使用proxy
代理1 2 3 4 5 6 7 8 9 10 11
module.exports = { // ... devServer: { proxy: { '/api': { target: 'http://localhost:3000', pathRewrite: {'^/api' : ''} } } } }
同域名下请求到 /api/orders 将会被代理到请求 http://localhost:3000/orders
sevServer.port:
指定要监听的端口号1 2 3
devServer: { port: 8000 }
sevServer.publicPath :
用于指定构建好的文件在浏览器中用什么路径去访问,默认是’/’,假设运行在 http://localhost:8000 ,并且output.filename
被设置为bundle.js,则此包可通过http://localhost:8000/bundle.js 访问,也可修改指定目录访问或使用一个完整的URL。1 2 3
devServer: { publicPath: '/assets/' }
sevServer.contentBase :
用于配置提供额外静态文件内容的目录,即不经过wabpack构建,但是需要在webpack-dev-server中提供访问的静态资源。
publicPath
的优先级高于contentBase
,publicPath是配置构建好的结果以什么路径去访问,而contentBase是配置额外的静态内容的访问路径。1 2 3
devServer: { contentBase: path.join(__dirname, 'public') }
1 2 3 4
devServer: { // 多个目录提供内容 contentBase: [path.join(__dirname, 'public'), path.join(__dirname, 'assets')] }
devServer.after:
在服务内部所有其它中间件之后,提供执行自定义中间件的功能。1 2 3 4 5
devServer: { after: function(app, server) { //do something } }
devServer.before:
在服务内部的所有其他中间件之前,执行自定义中间件的功能。1 2 3 4 5 6 7
devServer: { before: function(app, server) { app.get('/some/path', function(req, res){ res.json({ custom: 'response'}) }) } }
更多配置请看dev-server
核心概念
entry
多个代码模块中会有一个起始的.js
文件,这便是webpack构建的入口,常见项目中可能入口有一个也可能有多个。
- 单个入口
1 2 3 4 5
module.exports = { entry: { main: './src/index.js' } }
- 多个入口
1 2 3 4 5 6 7 8
module.exports = { entry: { main: [ './src/foo.js', './src/bar.js' ] } }
resolve
- resolve.alias
模糊匹配:如import ‘utils/query.js’ 等同于 ‘import [项目绝对路径]/src/utils/query.js’1 2 3
alias: { utils: path.resolve(__dirname, 'src/utils') }
精确匹配:给定对象的键后末尾添加$,如匹配 import ‘utils’
1 2 3
alias: { utils$: path.resolve(__dirname, 'src/utils') }
- resolve.extensions
此配置是一个数组,数组的顺序代表匹配后缀的优先级1
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx', '.css']
如在src/utils下面有一个common.js文件时,就可以这样引用 import * as common from ‘utils/common’
- resolve.modules
告诉webpack解析模块时应该搜索的目录,绝对路径和相对路径都能使用 相对路径将类似于 Node 查找 ‘node_modules’ 的方式进行查找 使用绝对路径,将只在给定目录中搜索1 2 3
resolve: { modules: ['node_modules'] }
可以添加目录到模块搜索目录,此目录优先级高于node_modules,这样配置可以在某种程度上简化模块的查找,提升构建速度
1 2 3
resolve: { modules:[path.resolve(_dirname, 'src'), 'node_modules'] }
- resolve.mainFiles
默认解析使用index.js这个文件,也可配置1 2 3
resolve: { mainFiles: ['index'] // 可以添加其他默认使用的文件名 }
- resolve.mainFields
?
更多关于resolve
,请移步官网
loader
loader就是webpack提供的一种处理多种格式文件格式的机制,可以理解是一个转换器,负责把某种文件格式的内容转换为webpack可以支持打包的模块。
默认webpack会把所有的依赖打包成js文件,当我们需要处理不同类型的文件时可以在module.rules
配置
-
条件
{test: Condition}
匹配特定条件{include: Condition }
匹配特定条件{exclude: Condition }
排除特定条件{and: Condition }
必须匹配数组中的所有条件{or: Condition }
匹配数组中任何一个条件{not: Condition }
必须排除这个条件
1
2
3
4
5
6
7
8
9
10
11
rules: [
{
test: /\.jsx?/, //正则,
include: [
path.resolve(_dirname, 'src') // 字符串,注意是绝对路径
],
not: [
(value) => {/*...*/ return true;} // 函数,高度自定义
]
}
]
- 模块类型
不同的模块类型类似于配置了不同的
loader
,webpack会针对性的进行处理,目前有5种模块类型:
javascript/auto
支持现有各种JS代码模块类型————CommonJS、AMD、ESMjavascript/esm
ECMAScript modules,例如CommonJS和AMD等不支持,是.mjs文件的默认类型javascript/dynamic
CommonJS和AMD,排除ESMjavascript/json
JSON格式数据,是.json的文件默认类型webassembly/experimental
WebAssembly modules 是.wasm文件的默认类型
举例,如所有JS代码文件都设置为强制使用ESM类型:
1
2
3
4
5
6
7
{
test: /\.js/,
include: [
path.resolve(_dirname, 'src),
],
type: 'javascript/esm'
}
- 使用loader
module.rules 的匹配规则主要是用于匹配loader,使用
use
字段:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
rules: [ { test: /\.less/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1 //数字1代表当前loader之后的一个loader } }, { loader: 'less-loader', options: { noIeCompat: true } } ] } ]
一个模块文件可以经过多个loader的转换处理,在同一个rule中执行顺序是从最后配置的loader开始往前。 上述从后往前的顺序是在同一个rule中进行,那如果多个rule匹配同一个模块文件,此时的loader的应用顺序又是怎样的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13
rules: [ { enforce: 'pre', test: /\.js$/, exclude: /node_modules/, loader: 'eslint-loader' }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ]
webpack提供了
enforce
字段来配置当前rule的loader类型,倘若没配置就是普通类型,还可配置pre
或post
,分别对应前置类型和后置类型的loader - tips:还有一种行内loader,即如
const json=require('json-loader!./file.json')
,不建议在开发中使用
总结下来,loader按照前置->行内->普通->后置的顺序执行
- module.noParse
用于配置哪些模块文件的内容不需要解析,忽略的文件不应该含有import,require,define的调用,或其他导入机制,可以提高整体的构建速度
1 2 3 4 5
module.exports = { module: { noParse: /jquery|lodash/ } }
plugin
用于处理更多其他的一些构建任务,通过添加我们需要的plugin,可以满足更多构建任务中的特殊需求。
webpack内置的插件可以通过webpack.插件名
获取,内置插件,非内置插件需要先在package.json
安装下
例如,要使用压缩JS代码的uglifyjs-webpack-plugin
插件,只需在配置中通过plugins
字段添加新的plugin即可:
1
2
3
4
5
6
7
const UglifyPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyPlugin()
]
}
- DefinePlugin
用途:允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和生产模式的构建允许不同的行为非常有用
用法:每个传进 DefinePlugin 的键值都是一个标志符或者多个用 . 连接起来的标志符。
如果这个值是一个字符串,它会被当作一个代码片段来使用。
如果这个值不是字符串,它会被转化为字符串(包括函数)。
如果这个值是一个对象,它所有的 key 会被同样的方式定义。
如果在一个 key 前面加了 typeof,它会被定义为 typeof 调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugins: [
new webpack.DefinePlugin({
'process.host': getDeployConfigDefine(),
VERSION: 'v1'
})
]
// ....
function getDeployConfigDefine() {
let config = {}
Object.keys(env).forEach(function(key) {
config[key] = `'${env[key].api}'`
})
return config
}
1
console.log(VERSION)
- copy-webpack-plugin
用途:将不经过webpack处理,但我们希望也能出现在build目录下的文件,使用CopyWebpackPlugin处理
用法:1 2 3 4 5 6 7 8 9 10 11
const CopyWebpackPlugin = require('copy-webpack-plugin') const resolve = (file) => path.resolve(__dirname, file) plugins: [ new CopyWebpackPlugin([ { from: resolve('./static'), // 配置来源 to: to: resolve('./dist/static'), // 配置目标路径 } ]) ]
更多配置请参考copy-webpack-plugin
- extract-text-webpack-plugin
用途:用来将依赖的CSS分离出来成为单独的文件
用法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
const ExtracTextPlugin = require('extract-text-webpack-plugin') module.exports = { // ... module:{ rules: [ { test: /\.css$/, // 因为此插件需要干涉模块转换的内容,所以需要使用它对应的loader use: ExtracTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) } ] } plugins: [ new ExtracTextPlugin('index.css') ] }