基础篇

基础配置

首先安装依赖 npm install webpack webpack-cli -D

webpack4可以0配置打包 直接运行 npx webpack 会到bin下面去找webpack 然后进行默认配置的打包

用来打包其实就是支持js的模块化

默认配置文件命名 webpack.config.js和webpackfile.js,手动指定配置文件的时候要—config xxxx

模式(mode)有两种production和development,默认是生产,production会压缩文件进行优化,development 开发环境,可以看到打包后的结果,不会进行压缩和优化的操作

问:webpack打包后的结果为什么可以在浏览器中执行?

1
2
3
4
5
6
7
8
9
10
//基础配置
module.exports = {
entry:'./src/index.js',
mode:'development',
output:{
//有更改的时候会hash修改
filename:'bundle.[hash].js',
path:path.resolve('dist') //path必须是一个绝对路径
}
}

文件解析

分析一个最简单的打包出来的bundle文件

webpack内部实现了一个require的方法,实现了递归的依赖关系。从入口文件开始加载模块(加载的时候会存进缓存,如果缓存中有了就不install这个模块),加载模块后,会call执行这个模块(key:value的形式组合的 modules[moduleId].call(…) moduleId就是key),然后找到了这个模块里还引入别的模块,就继续require,exports结果。

HTML plugin

配置devserver开发服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
entry:'./src/index.js',
//还可以配置压缩、open、地址等等
devServer:{
port:3000,
contentBase:'./dist'
},
mode:'development',
output:{
filename:'bundle.[hash].js',
path:path.resolve('dist') //path必须是一个绝对路径
}
}

webpackHtmlPlugin

在我们的项目中添加一个index.html作为模板,把打包出来的文件放进这个html中展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//...,
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
filename:'index.html',
//可以配置一些压缩 比如去掉双引号 折叠空行
minify:{
collapseWhitespace:true
},
hash:true
})
]
}

样式处理

使用各种loader去处理对应的文件类型

css-loader 处理css文件 主要负责解析@import这种语法

style-loader 将解析好的css放进style标签里插入到html中

常见预处理器:

  • less less-loader

  • node-sass sass-loader

  • stylus stylus-loader

自动添加浏览器前缀:

postcss-loader 配置autoprefixer

抽离css样式:

mini-css-extract-plugin

抽离后的css可以利用optimize-css-assets-webpack-plugin来压缩 配置在webpack的优化项中

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
//...,
optimization:{
minimizer:{
//要注意如果minimizer修改了之后要把js压缩手动添加上去 不然js压缩会失效
new UglifyJsPlugin({
...
}),
new OptimizeCss()
}
}
}

转化es6语法 babel 处理js语法

使用babel转化语法 首先安装

babel-loader 处理的loader

@babel/core 这个是babel的核心模块

@babel/preset-env 这是个转化模块,可以把一些高级的语法转化成低级的语法

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
module.exports = {
//...,
module:{
rules:[
{
test:'/\.js$/',
exclude:'/node_module/',
use:{
loader:'babel-loader',
options:{
//所有预设
presets:[
'@babel/preset-env'
],
//很多更加高级的语法预设中没有 就要自己添加 比如class 装饰器
plugins:[
"@babel/plugin-proposal-class-properties",
//预设可以转化代码但是不能加上es6内置的方法
//所以要加入这个插件来 对应上内置的方法。
//但是有一个不能转换include include是实例上的 要用polyfill
"@babel/plugin-transform-runtime",
]
}
}
}
]
}
}

全局变量引入问题

比如我们引入jquery 直接import $ from 'jquery' 可以打出$的值,但是window.$ 不能打出来,没挂在window上。想要挂上去可以

1.expose-loader

内联loader

暴露到window上(在引入的页面或者webpack的loader里面加内联loader都可以)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//方法一:内联loader
import $ from "expose-loader?$!jquery"
console.log(window.$)
//方法二:webpack config中配置 但是页面里还是要import $ from "jquery"的
module.exports = {
//...,
module:{
rules:[
{
//代码中引用了jq
test:require.resolve('jquery'),
use:'expose-loader?$'
}
]
}
}

2..ProvidePlugin

不想在组件里引入jq,那就用这个方法在每个模块中注入$ 就可以直接用$不需要引入了

1
2
3
4
5
6
7
module.exports = {
plugins:[
new webpack.ProvidePlugin({
$:'jquery'
})
]
}

3.引入cdn 且external掉jq

运用的地方用了import进来 不用的话其实就不会被打包进来,cdn的时候其实已经引入了 重新import一下等于又引入一遍

用external可以让引入的包不被打包

图片处理

图片的引用有三种 1.js中 2.css中 3.html中

1.通过配置file-loader来读取图片

1
2
3
4
5
6
7
8
9
10
//js
import logo from "./logo.png"; //引入获取的是新的图片地址(hash的)
let image = new Image();
image.src = logo;
//config
{
test:/\.(png|jpg|jpeg|gif)$/,
use:'file-loader'
}

2.css中是可以直接使用的,因为css-loader处理了这个问题,会去把括号里的路径引入进来

伪代码:background:url(‘../img/logo.png’) -> background:url(require(‘../img/logo.png’))

3.使用html-withing-loader来转化html中图片的路径

1
2
3
4
{
test:/\.html$/,
use:'html-withing-loader'
}

但是在我们实际开发过程中,很多图片都比较小,其实是不希望这些小图也发个http请求的,所以就采用一定大小内使用base64(但是base64的大小会比原来的图片大大约三分一),超过大小发请求的方式,使用url-loader可以实现。

1
2
3
4
5
6
7
8
9
{
test:/\.(png|jpg|jpeg|gif)$/,
use:{
loader:'url-loader',
options:{
limit:8192
}
}
}

打包文件分类

可以在不同的output的时候加上文件夹路径

1
2
3
4
5
6
7
8
9
10
11
12
//比如url-loader里
{
test:/\.(png|jpg|jpeg|gif)$/,
use:{
loader:'url-loader',
options:{
limit:8192,
name: 'images/[hash:8].[name].[ext]'
}
}
}
//minicss中也是 filename:'css/[name].css'之类

如果要统一在打包路径前面加内容,比如cdn,比如之前项目中使用dll打出来的前面少’ ./ ‘,可以在output中配置publicPath,打包出来文件引用的时候就会前面加上这个publicPath了(同理,如果不想全部引用都加的话,可以在想要加的地方配置publicPath,比如图片就在url-loader中配置)

配置篇

打包多页面应用

多个entry多个chunk,对应多个html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
entry:{
entry1:path.resolve('./src/entry1.js'),
entry2:path.resolve('./src/entry2.js'),
},
...
plugins:[
new HtmlWebpackPlugin({
template:'index.html',
title:'111',
chunks:['entry1'],
filename:'entry1.html'
}),
new HtmlWebpackPlugin({
template:'index.html',
title:'222',
chunks:['entry2'],
filename:'entry2.html'
})
]
}

配置devtool

对source-map的配置

image-20181223210848403

eval:提高构建效率 (映射到构建后的代码)

cheap:sourcemap没有列信息,大幅提高 sourcemap 生成的效率

module:简化loader的sourcemap(不包含列信息)

watch(check一下)

监控代码的变化,实时打包

1
2
3
4
5
6
7
8
{
watch:true,
watchOptions:{
poll:1000, //每秒监控1000次
aggregateTimeout:500 //防抖 一直输入的话不会触发 停止后过了500ms才触发
ignored: /node_modules/
}
}

常用小插件

1.clean-webpack-plugin clean文件夹

2.copy-webpack-plugin 拷贝文件

3.webpack.bannerPlugin 为每个 chunk 文件头部添加 banner

跨域问题解决

1.devserver中配置proxy代理

配置target,/api开头的都去 http://localhost:4000找

1
2
3
4
5
6
7
8
9
devServer: {
port: 3000,
contentBase: './dist',
proxy:{
'/api':{
target:'http://localhost:4000'
}
}
}

2.如果只是想要前端这边自己mock点数据,可以使用devserver的before方法

1
2
3
4
5
6
7
8
devServer:{
//app就是express实例化的
before(app){
app.get('/user',(req,res)=>{
res.json({name:'huk'})
})
}
}

3.不用代理来处理,在服务端启动webpack,并且端口用服务端的端口

服务器端启动webpack 使得前后端启动在一个端口上 express加上中间件 启动webpack devserver(webpack的compile传进sever 在middlerware中启动)

1
2
3
4
5
6
7
8
9
10
11
12
let express = require('express');
let app = express();
let webpack = require('webpack');
let middle = require('webpack-dev-middleware');
let config = require('./webpack.config');
let compiler = webpack(config);
app.use(middle(compiler))
app.get('/api/user',(req,res)=>{
res.json({name:'hahaha'})
})
app.listen(4000)

resolve配置

1
2
3
4
5
6
7
8
9
10
11
12
13
{
resolve:{
//从哪里开始依赖 避免一直往上查
modules:[path.resolve('node_modules')],
//别名 比如import bootstrap的时候,其实是引入的bootstrap.js,这又要依赖jq
//这样别名可以直接引入某个文件
alias:{
bootstrap:'bootstrap/dist/css/bootstrap.css'
},
//自定义扩展名的优先级 默认不写扩展名是读的.js 这边配了之后会往后找
extensions:['.js','.css','.json']
}
}

环境变量定义

webpack.DefinePlugin

1
2
3
new webpack.DefinePlugin({
'process.env.api':JSON.stringify(process.env.api)
})

webpack.EnvironmentPlugin

用起来比较方便,把上面DefinePlugin的写法简化了

区分不同环境

利用webpack-merge配置多个config文件。比如,dev和prod环境下有不同的配置,就把公共地方的写一份webpack.base.config.js,在dev和prod config中merge base,然后加上这个环境下独有的配置

  • webpack.base.config.js
  • webpack.dev.config.js
  • webpack.prod.config.js

优化

列举下可以考虑的点儿,用法不赘述了,可以去查对应的文章。这块准备重新写一篇,太多了。

  • happypack 多线程打包loader

  • dll 打包三方依赖

    项目有业务代码和三方库,三方库比较稳定,但是在构建的时候也会重新构建,这样减慢了构建速度。使用dll可以把常用的三方库放进动态链接库中,构建时候不需要重新打包。

    DllPlugin+DllReferencePlugin dll打包的操作是在项目打包之前做的,然后可以用html-webpack-include-assets-plugin把打包出来的dll插入到想要插入的html中

    或者可以使用autodll plugin,这插件可以把上面的步骤融合进来,不用自己去配了,但是在多页面的情况下比较局限性,比如想打两个dll文件,一个每个打出来的html都用,另一个只给某一个html用,这就不行了,需要自己改下autodll的源码,让inject支持匹配。

  • webpack自带优化项

    配置optimization

  • IgnorePlugin

    可以指定某个东西不打包进去 比如moment打包进来的时候 里面有个local的语言包 我们不需要那么多的语言包 所以可以打包时忽略掉这个文件夹 (如果要引入别的语言包可以在项目内单独import里面的某个语言包然后设置语言)

  • noParse 不去解析某个包中的依赖库

  • 热更新