webpack
核心概念
webpack的四个核心概念:entry、output、loader、plugins
mode
mode 通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化1
mode: 'production'
也可以从cli参数中传递
1 | webpack --mode=development/production |
entry
entry 指示webpack应该使用哪个模块,来作为其构建内部依赖图的开始。进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。
entry 的配置值可以是string | object | array
可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src。1
2
3
4entry: {
path.join(__dirname, '../src/index.js')
}
/* 此处用了node的path模块,join方法用于连接多个目录,自动区分windows和linux的连接符;__dirname是node的一个全局变量,获得当前文件所在目录的完整目录名 */
output
output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
output 的最低要求是,将它的值设置为一个对象:
- filename 设置输出文件名;
- path 设置目标输出目录的绝对路径。
1
2
3
4output: {
filename: 'bundle.js',
path: path.join(__dirname, '../dist')
}
loader
loader 用于对模块的源代码进行转换。用于转换某些类型的模块,让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。
在配置loader时:
- test 用于标识出应该被对应的 loader 进行转换的某个或某些文件;
- use 属性,表示进行转换时,应该使用哪个 loader。
1 | module: { |
除了通过配置指定loader,还可以通过内联和cli指定(不推荐)
除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段(不明白?)
loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译和其他更多(O__O “…)
plugins
plugins 用于执行范围更广的任务,从打包优化和压缩,一直到重新定义环境中的变量。
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。
也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。
1 | const VueLoaderPlugin = require('vue-loader/lib/plugin') |
配置
webpack 的配置文件,是导出一个对象的 JavaScript 文件。此对象,由 webpack 根据对象定义的属性进行解析。
可以用数组,导出多个配置对象。
构建目标
1 | target: 'node' |
项目实践
从零开始,搭建一个最简单的vue项目。虽然现在有各种脚手架为我们定制了完美的方案,但是总有一种摸不到深层的不安全感,亲自深入了解一下,可以对自己所做的事情有更多的掌控,这种感觉才是美好滴。
步骤:
创建项目文件夹
1
mkdir test-pack && cd test-pack
初始化,生成package.json
1
npm init
安装依赖项
1
2
3
4
5npm install webpack webpack-cli webpack-dev-server --save-dev
npm install clean-webpack-plugin html-webpack-plugin webpack-merge --save-dev
npm install vue --save
npm install vue-loader vue-template-compiler --save-dev
npm install style-loader css-loader --save-dev
安装完成后,各依赖项如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15"devDependencies": {
"clean-webpack-plugin": "^2.0.1", // build之前先清空dist目录
"css-loader": "^2.1.1",
"html-webpack-plugin": "^3.2.0", // 自动管理输出的index.html文件
"style-loader": "^0.23.1",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.3.0",
"webpack-merge": "^4.2.1" // 处理通用配置
},
"dependencies": {
"vue": "^2.6.10"
}
组织文件结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16"test-pack": {
"node_modules"
"dist"
"config": {
"webpack.base.js"
"webpack.dev.js"
"webpack.prod.js"
}
"src": {
"components": {
"app.vue"
}
"index.js"
}
"package.json"
}app.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<template>
<div class="test">{{text}}</div>
</template>
<script>
export default {
data() {
return {
text: '测试'
}
}
}
</script>
<style>
.test {
color: red;
}
</style>index.js
1
2
3
4
5
6
7
8
9import Vue from 'vue'
import App from './components/app.vue'
const root = document.createElement('div')
document.body.appendChild(root)
new Vue({
render:(h) => h(App)
}).$mount(root)在webpack.base.js中书写通用配置
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
30const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.join(__dirname, '../src/index.js'),
output: {
filename: '[name].[chunkhash].js', // hash值确保浏览器拿取最新文件
path: path.join(__dirname, '../dist')
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HTMLPlugin({title: '测试'})
]
}在webpack.dev.js中书写开发环境的配置,让代码在本地跑起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const merge = require('webpack-merge')
const base = require('./webpack.base')
const webpack = require('webpack')
module.exports = merge(base, {
mode: 'development',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
devServer: {
port: 8000,
host: '0.0.0.0',
hot: true,
overlay: {
error: true
}
}
})在package.json中添加运行本地服务的脚本命令
1
2
3"scripts": {
"dev": "webpack-dev-server --config config/webpack.dev.js"
}
此时,运行npm run dev,可在本地跑起来,并支持热更新
在webpack.build.js中书写生产环境的打包配置
1
2
3
4
5
6
7
8
9
10const merge = require('webpack-merge')
const base = require('./webpack.base')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = merge(base, {
mode: 'production',
plugins: [
new CleanWebpackPlugin()
]
})在package.json中添加打包的脚本命令
1
2
3
4"scripts": {
"dev": "webpack-dev-server --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
}
此时,运行 npm run build,可以看到文件被打包为bundle.js放在dist目录下,同时自动生成index.html,点击运行index.html可以看到网页跟本地运行一样的效果
- 由于添加了html-webpack-plugin插件,在dist目录下自动生成了index.html
1
2
3
4
5
6
7
8
9
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<script type="text/javascript" src="main.40cda2cd05192f9fc326.js"></script></body>
</html>
打开index.html可以查看效果
至此,一个最简单的从零搭建项目的流程就走完了,后续可以添加更多东西以使项目变得更加完美。
tips
- 通过安装依赖,可以使用不同的语言来书写配置文件,如typeScript/coffeeScript/jsx/babel等
- 除了导出单个配置对象,还可以导出一个函数、Promise、多个对象
- entry 简单规则:每个 HTML 页面都有一个入口起点,单页应用(SPA):一个入口起点,多页应用(MPA):多个入口起点
- 错误处理机制
- loader 总是从右到左地被调用(loader的调用很有讲究,可以抽空研究)
- 模块方法:ES6/CommonJS/AMD
- 编写一个插件
- package.json里面移除”main”: “index.js”,添加”private”: true,防止意外发布代码
- 图片等静态资源可以统一放到一个公共的地方,不过为了方便引入,可以使用alias修改路径
- Manifest可以对“模块映射到输出 bundle 的过程”进行追踪
- 通常为不同的环境编写不同的配置,可以用 webpack-merge 处理通用配置,不必重复代码
- 建议在生产环境中使用 uglifyjs-webpack-plugin 和 inline-source-map
- CommonsChunkPlugin 提取公共 bundle ,防止代码重复
- webpack-chart 等 bundle 分析工具
- 离线PWA/workBox
- 使用环境变量NODE_ENV
- webpack 可以跟其它工具集成
- loaders
- plugins
一点感悟
- 工作中做项目时,前辈们早已搭好环境,抑或网上早有官方出品的各种“完美”脚手架,但是只有亲自研究一番,才拥有真正属于自己一份信心与豁然;
- 日常工作每天都有接触,照理说随便花点时间看看资料就明白了,但是一旦开始一个新的目标,我喜欢把它吃透一点,生怕错过什么了不得的风景。事实证明,我的想法是对的。刚开始只花了几个小时就能自己搭建一个环境出来,本来满心欢喜地准备收工,但心里总不踏实,于是耐着性子把官方所有文档都看了一遍,再从网上找了视频来看。在看文档的过程中,才发现自己最先搭的环境很粗糙,后来添加了 html-webpack-plugin 来自动生成index.html文件,添加了 clean-webpack-plugin 来清理dist目录,添加了 webpack-merge 来处理重复代码……这样,项目才一步一步变成自己喜欢的样子。在看视频的时候,也是发现了些新大陆,比如,node_modules 目录下的 .bin 放的是可执行文件,直接点击里面的文件,可以实现点击应用程序图标的效果,虽然这些不是什么了不得的东西,但是,重新拾起这些被忽略的点,也是很酷的呀。