1.什么是WebPack
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
构建就是把源代码转换成发布到线上的可执行 JavaScrip、CSS、HTML 代码,包括如下内容。
- 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。 构建给前端开发注入了更大的活力,解放了我们的生产力
2.初始化项目
mkdir webpack-start
cd webpack-start
npm init
3.快速上手
3.1 webpack核心概念
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。
- Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。
在使用webpack进行打包的时候,默认情况下会将src下的入口文件(index.js)进行打包
// node v8.2版本以后都会有一个npx
// npx会执行bin里的文件
npx webpack // 不设置mode的情况下 打包出来的文件自动压缩
npx webpack --mode development // 设置mode为开发模式,打包后的文件不被压缩
当执行npx webpack命令的时候,webpack会自动查找项目中src目录下的index.js文件,然后进行打包,生成一个dist目录并存在一个打包好的main.js文件
3.2 配置webpack
在项目下创建一个webpack.config.js(默认,可修改)文件来配置webpack
npm install webpack webpack-cli -D
- 创建src
- 创建dist
- 创建index.html
- 配置文件webpack.config.js
- entry:配置入口文件的地址
- output:配置出口文件的地址
- module:配置模块,主要用来配置不同文件的加载器
- plugins:配置插件
- devServer:配置开发服务器
// webpack.config.js
const path = require('path');
module.exports = {
entry: '', // 入口文件
output: {}, // 出口文件
module: {}, // 处理对应模块
plugins: [], // 对应的插件
devServer: {}, // 开发服务器配置
mode: 'development' // 模式配置
}
4. 配置开发服务器
npm i webpack-dev-server –D
首先起服务之前,要把打包的入口和出口文件都配置好
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 打包后的文件名称
path: path.resolve('dist') // 打包后的目录,必须是绝对路径
}
}
配置默认服务的配置
- contentBase 配置开发服务运行时的文件根目录
- host:开发服务器监听的主机地址
- compress 开发服务器是否启动gzip等压缩
- port:开发服务器监听的端口
devServer:{
contentBase:path.resolve(__dirname,'dist'),
host:'localhost',
compress:true,
port:8080
}
"scripts": {
"build": "webpack --mode development",
"dev": "webpack-dev-server --open --mode development "
}
package.json文件
npm run build就是我们打包后的文件,这是生产环境下,上线需要的文件
npm run dev是我们开发环境下打包的文件,当然由于devServer帮我们把文件放到内存中了,所以并不会输出打包后的dist文件夹
通过npm run build之后会生成一个dist目录文件夹,就和上面打包后的样子一样了
多入口文件
这里要多加一句,有的时候文件多的时候,要配置多文件的入口,出口
// 1.写成数组的方式就可以打出多入口文件,不过这里打包后的文件都合成了一个
// entry: ['./src/index.js', './src/login.js'],
// 2.真正实现多入口和多出口需要写成对象的方式
entry: {
index: './src/index.js',
login: './src/login.js'
},
output: {
// 1. filename: 'bundle.js',
// 2. [name]就可以将出口文件名和入口文件名一一对应
filename: '[name].js', // 打包后会生成index.js和login.js文件
path: path.resolve('dist')
}
这时候执行npm run build后,会生成打包好的两个js文件,如图所示
5. 支持加载css文件
5.1 什么是Loader
通过使用不同的Loader,Webpack可以要把不同的文件都转成JS文件,比如CSS、ES6/7、JSX等
- test:匹配处理文件的扩展名的正则表达式
- use:loader名称,就是你要使用模块的名称
- include/exclude:手动指定必须处理的文件夹或屏蔽不需要处理的文件夹
- query:为loaders提供额外的设置选项
loader三种写法
- use
- loader
- use+loader
5.2 css-loader
npm i style-loader css-loader -D
// 引入less文件的话,也需要安装对应的loader
npm i less less-loader -D
配置加载器
module: {
rules: [
{
test: /\.css$/, // 解析css
use: ['style-loader', 'css-loader'] // 从右向左解析
/*
也可以这样写,这种方式方便写一些配置参数
use: [
{loader: 'style-loader'},
{loader: 'css-loader'}
]
*/
include:path.join(__dirname,'./src'),//对哪些css文件处理
exclude:/node_modules/
}
]
}
6. 自动产出html
文件都打包好了,但是我们在使用的时候不能在dist目录下去创建一个html文件,然后去引用打包后的js吧,这不合理,实际开发中也不会这样
我们需要实现html打包功能,可以通过一个模板实现打包出引用好路径的html来这就需要用到一个常用的插件了,html-webpack-plugin
,用之前我们来安一下它
npm i html-webpack-plugin -D
这里要在配置文件中,从头上引入一下这个插件
// 插件都是一个类,所以我们命名的时候尽量用大写开头
let HtmlWebpackPlugin = require('html-webpack-plugin');
- minify 是对html文件进行压缩,removeAttrubuteQuotes是去掉属性的双引号
- hash 引入产出资源的时候加上哈希避免缓存
- template 模版路径
plugins: [
// 通过new一下这个类来使用插件
new HtmlWebpackPlugin({
// 用哪个html作为模板
// 在src目录下创建一个index.html页面当做模板来用
template: './src/index.html',
hash: true, // 会在打包好的bundle.js后面加上hash串
})
]
通过上面的配置后,我们再npm run build打包看一下现在是个什么样子了
如果开发的时候不只一个页面,我们需要配置多页面,就多复制几个HtmlWebpackPlugin插件
用chunks来对着相应的入口
let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 多页面开发,怎么配置多页面
entry: {
index: './src/index.js',
login: './src/login.js'
},
// 出口文件
output: {
filename: '[name].js',
path: path.resolve('dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'] // 对应关系,index.js对应的是index.html
}),
new HtmlWebpackPlugin({
template: './src/login.html',
filename: 'login.html',
chunks: ['login'] // 对应关系,login.js对应的是login.html
})
]
}
7. 支持图片
7.1 手动添加图片
npm i file-loader url-loader -D
- file-loader 解决CSS等文件中的引入图片路径问题
- url-loader 当图片较小的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader 进行拷贝
在rules中加上图片的处理
{
test:/\.(jpg|png|gif|svg)$/,
use:'url-loader',
include:path.join(__dirname,'./src'),
exclude:/node_modules/
}
7.2 在CSS中引入图片
在css中指定了publicPath路径这样就可以根据相对路径引用到图片资源了
7.3 页面img引用图片
页面中经常会用到img标签,img引用的图片地址也需要一个loader来帮我们处理好
npm i html-withimg-loader -D
rules: [
{
test: /\.(htm|html)$/,
use: 'html-withimg-loader'
}
]
这样再打包后的html文件下img就可以正常引用图片路径了
8.编译less 和 sass
npm i less less-loader -D
npm i node-saas sass-loader -D
const cssExtract=new ExtractTextWebpackPlugin('css.css');
const lessExtract=new ExtractTextWebpackPlugin('less.css');
const sassExtract=new ExtractTextWebpackPlugin('sass.css');
{
test:/\.less$/,
use: lessExtract.extract({
use:['css-loader','less-loader']
}),
include:path.join(__dirname,'./src'),
exclude:/node_modules/
},
{
test:/\.scss$/,
use: sassExtract.extract({
use:['css-loader','sass-loader']
}),
include:path.join(__dirname,'./src'),
exclude:/node_modules/
},
9. 处理CSS3属性前缀
为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀
- Trident内核:主要代表为IE浏览器, 前缀为-ms
- Gecko内核:主要代表为Firefox, 前缀为-moz
- Presto内核:主要代表为Opera, 前缀为-o
- Webkit内核:产要代表为Chrome和Safari, 前缀为-webkit
npm i postcss-loader autoprefixer -D
在这里还要引入autoprefixer插件,并在postcss.config.js中写:
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
10.转义ES6/ES7/JSX
Babel其实是一个编译JavaScript的平台,可以把ES6/ES7,React的JSX转义为ES5
npm i babel-core babel-loader babel-preset-env babel-preset-stage-0 babel-preset-react -D
当把这些都安好后,我们就开始配置,由于要兼容的代码不仅仅包含ES6还有之后的版本和那些仅仅是草案的内容,所以我们可以通过一个.babelrc文件来配置一下,对这些版本的支持
.babelrc
{
"presets": ["env", "stage-0"] // 从右向左解析
}
postcss.config.js
{
test:/\.jsx?$/,
use: {
loader: 'babel-loader',
},
include:path.join(__dirname,'./src'),
exclude:/node_modules/
},
在我们每次npm run build的时候都会在dist目录下创建很多打好的包,如果积累过多可能也会混乱
所以应该在每次打包之前将dist目录下的文件都清空,然后再把打好包的文件放进去这里提供一个clean-webpack-plugin插件
npm i clean-webpack-plugin -D
let CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
plugins: [
// 打包前先清空
new CleanWebpackPlugin('dist')
]
}
11. watch
当代码发生修改后可以自动重新编译
new webpack.BannerPlugin('hahaha'),
watch: true,
watchOptions: {
ignored: /node_modules/, //忽略不用监听变更的目录
aggregateTimeout: 500, //防止重复保存频繁重新编译,500毫米内重复保存不打包
poll:1000 //每秒询问的文件变更的次数
},
12. 压缩JS
压缩JS可以让输出的JS文件体积更小、加载更快、流量更省,还有混淆代码的加密功能
npm i uglifyjs-webpack-plugin -D
plugins: [
new UglifyjsWebpackPlugin(),
13.压缩CSS
webpack可以消除未使用的CSS,比如bootstrap中那些未使用的样式
npm i -D purifycss-webpack purify-css
npm i bootstrap -S
webpack.config.js
{
test:/\.css$/,
use: cssExtract.extract({
use: [{
loader: 'css-loader',
options:{minimize:true}
},'postcss-loader']
}),
},
`
`
`
`
new PurifyCSSPlugin({
//purifycss根据这个路径配置遍历你的HTML文件,查找你使用的CSS
paths:glob.sync(path.join(__dirname,'src/*.html'))
}),
14. 服务器代理
如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。
//请求到 /api/users 现在会被代理到请求 http://localhost:9000/api/users。
proxy: {
"/api": {
target: "http://localhost:9000",
pathRewrite: {"^/api":""}
}
}
15. resolve解析
15.1 extensions
指定extension之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配
resolve: {
//自动补全后缀,注意第一个必须是空字符串,后缀一定以点开头
extensions: [" ",".js",".css",".json"],
},
15.2 alias
配置别名可以加快webpack查找模块的速度
- 每当引入jquery模块的时候,它会直接引入jqueryPath,而不需要从node_modules文- 件夹中按模块的查找规则查找
- 不需要webpack去解析jquery.js文件
const bootstrap=path.join(__dirname,'node_modules/bootstrap/dist/css/bootstrap.css')
resolve: {
alias: {
'bootstrap': bootstrap
}
}
这里具体写法
module.exports = {
resolve: {
// 别名
alias: {
$: './src/jquery.js'
},
// 省略后缀
extensions: ['.js', '.json', '.css']
},
}
16. 区分环境变量
许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === ‘production’ 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:
npm install cross-env -D
package.json
"scripts": {
"build": "cross-env NODE_ENV=production webpack --mode development",
"dev": "webpack-dev-server --open --mode development "
}
webpack.config.js
plugins: [
new webpack.DefinePlugin({
NODE_ENV:JSON.stringify(process.env.NODE_ENV)
}),
index.js
if (process.env.NODE_ENV == 'development') {
console.log('这是开发环境');
} else {
console.log('这是生产环境');
}
17.externals
如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals
const $ = require("jquery");
const $ = window.jQuery;
`
`
`
`
externals: {
jquery: "jQuery"//如果要在浏览器中运行,那么不用添加什么前缀,默认设置就是global
}
18.服务器自动刷新
启动一个静态服务器,默认会自动刷新,就是说你对html,css,js文件做了修改并保存后,浏览器会默认刷新一次展现修改后的效果
正常情况下我们都是在开发环境中开发项目,所以之前配置的脚本”dev”可以派上用场了,在执行npm run dev命令后,会启动静态服务器,我们访问localhost:3000端口就可以看到开发的页面内容了
如果devServer里open设为true后,会自动打开浏览器
// webpack.config.js
let webpack = require('webpack');
module.exports = {
plugins: [
// 热替换,热替换不是刷新
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: './dist',
hot: true,//模块热替换
port: 3000
}
}
// 此时还没完虽然配置了插件和开启了热更新,但实际上并不会生效
// index.js
let a = 'hello world';
document.body.innerHTML = a;
console.log('这是webpack打包的入口文件');
// 还需要在主要的js文件里写入下面这段代码
if (module.hot) {
// 实现热更新
module.hot.accept();
}
- webpack定时获取文件的更新时间,并跟上次保存的时间进行比对,不一致就表示发生了变化,poll就用来配置每秒问多少次
- 当检测文件不再发生变化,会先缓存起来,等待一段时间后之后再通知监听者,这个等待时间通过aggregateTimeout配置
- webpack只会监听entry依赖的文件
- 我们需要尽可能减少需要监听的文件数量和检查频率,当然频率的降低会导致灵敏度下降
优化模块热替换浏览器日志
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
]
19.提取公共代码
19.1 为什么需要提取公共代码
大网站有多个页面,每个页面由于采用相同技术栈和样式代码,会包含很多公共代码,如果都包含进来会有问题
- 相同的资源被重复的加载,浪费用户的流量和服务器的成本;
- 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。 如果能把公共代码抽离成单独文件进行加载能进行优化,可以减少网络传输流量,降低服务器成本
19.2 如何提取
- 基础类库,方便长期缓存
- 页面之间的公用代码
- 各个页面单独生成文件
在webpack4之前,提取公共代码都是通过一个叫CommonsChunkPlugin的插件来办到的。到了4以后,内置了一个一模一样的功能,而且起了一个好听的名字叫“优化”
下面我们就来看看如何提取公共代码
// 假设a.js和b.js都同时引入了jquery.js和一个写好的utils.js
// a.js和b.js
import $ from 'jquery';
import {sum} from 'utils';
那么他们两个js中其中公共部分的代码就是jquery和utils里的代码了
可以针对第三方插件和写好的公共文件
module.exports = {
entry: {
a: './src/a.js',
b: './src/b.js'
},
output: {
filename: '[name].js',
path: path.resolve('dust')
},
// 提取公共代码
optimization: {
splitChunks: {
cacheGroups: {
vendor: { // 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
},
utils: { // 抽离自己写的公共代码,utils这个名字可以随意起
chunks: 'initial',
name: 'utils', // 任意命名
minSize: 0 // 只要超出0字节就生成一个新包
}
}
}
},
plugins: [
new HtmlWebpackPlugin({
filename: 'a.html',
template: './src/index.html', // 以index.html为模板
chunks: ['vendor', 'a']
}),
new HtmlWebpackPlugin({
filename: 'b.html',
template: './src/index.html', // 以index.html为模板
chunks: ['vendor', 'b']
})
]
}
20.webpack-dev-middleware 插件
webpack-dev-middleware 插件对更改的文件进行监控,编译,一般和 webpack-hot-middleware 配合使用,实现热加载功能
const path = require("path")
const express = require("express")
const webpack = require("webpack")
const webpackDevMiddleware = require("webpack-dev-middleware")
const webpackConfig = require('./webpack.config.js')
const app = express(),
DIST_DIR = path.join(__dirname, "dist"),// 设置静态访问文件路径
PORT = 9090, // 设置启动端口
complier = webpack(webpackConfig)
app.use(webpackDevMiddleware(complier, {
//绑定中间件的公共路径,与webpack配置的路径相同
publicPath: webpackConfig.output.publicPath,
quiet: true //向控制台显示内容
}))
// 这个方法和下边注释的方法作用一样,就是设置访问静态文件的路径
app.use(express.static(DIST_DIR))
app.listen(PORT,function(){
console.log("成功启动:localhost:"+ PORT)
})
结语
写的比较细,具体还要自己敲一遍