其实一开始是想着一步步看着官网英文教程demo和结合imooc的教程做下来,后来做了差不多第十节才发现居然有中文版的,风中凌乱。。。不过还是有选择性的记录一下整个学习的笔记,跟着都敲了一遍,详细看官网中文版。
一、webpack的优点
- 代码拆分
webpack有两种组织代码的方式 一种是同步一种是异步。异步依赖作为分割点,形成一个新的块。在优化过了依赖树后,每一个异步区块都作为一个文件被打包。 - loader
- 本身只处理原生的js,但是loader转化器可以将各种资源转换成为js模块。这样任何资源都可以成为 webpack的处理对象。比如处理css的cssloader,处理coffeescript coffeesecriptloader。
- 智能解析
webpack是一个智能解析器,可以处理几乎任何第三方的库,无论他们的模块形式是commonjs,amd 还是普通的js文件。甚至加载依赖的时候可以动态表达式
二、安装
npm init
# 全局安装
npm install webpack -g
# 安装在项目中
npm install --save-dev
PS:Windows下建议还是全局安装吧,全局安装配置一次环境变量就可以使用。若选择安装在项目中,则每一个项目都需要配置一下环境变量,如
D:\webpack-demo\node_modules\.bin
三、开始使用
- 开始一个demo文件夹
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack
- 在项目中新建文件,src用于保存源文件,dist(distribution)用于保存发布的代码,即打包生成的代码。
webpack-demo
|- package.json
+ |- /dist
+ |- index.html
+ |- /src
+ |- index.js
- index.js
function compoent(){
var ele=document.createElement('div');
ele.innerHTML="Hello Webpack!"
return ele;
}
document.body.appendChild(compoent());
- index.html
<html>
<head>
<title>Getting Started</title>
</head>
<body>
<!--bundle.js是下面将要根据index.js打包生成的文件-->
<script src="bundle.js"></script>
</body>
</html>
- 在项目根目录下,输入命令开始打包:
webpack 源文件 目标文件
,打包后可以在dist文件夹下看到自动生成了bundle.js。
webpack src/index.js dist/bundle.js
- 使用配置文件
- 在根目录添加webpack.config.js,直接运行webpack(不需要带参数),即可根据配置打包
const path=require('path');
module.exports={
entry:'./src/index.js',
output:{
path:path.resolve(__dirname, './dist'),
filename:'bundle.js'
}
}
PS:默认配置文件是
webpack.config.js
,修改默认的配置文件,用webpack --config webpack.dev.config.js
。
- 把脚本写进package.json,通过
npm run build
命令运行,如
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"webpack"
},
- 常用的参数
- 自动打包,加上
--watch
webpack src/hello.js dist/bundle.js --module-bind 'css=style-loader!css-loader' --watch
- 看到打包过程,
--progress
- 打包时候显示引用的模块,
--display-modules
- 查看为什么要打包,
--display-reasons
- 打包字体有颜色,
--colors
四、资源管理
- 处理css文件
- 安装css-loader和style-loader
npm install css-loader style-loader --save-dev
- 第一种方式:新建style.css,并在index.js引用,在文件中直接用
require('style-loader!css-loader!./style.css');
- 第二种方式:打包时候用参数的方式。
不在require中用style-loader!css-loader!的写法,则需要在打包的时候加上--module-bind
参数,即:webpack src/hello.js dist/bundle.js --module-bind 'css=style-loader!css-loader'
require('./style.css');
- 第三种方式:配置文件方式添加css-loader
const path=require('path');
module.exports={
entry:'./src/index.js',
output:{
path:path.resolve(__dirname, './dist'),
filename:'bundle.js'
},
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader'
]
}
]
}
}
使用postcss-loader和 autoprefixer
安装
npm install postcss-loader autoprefixer --save-dev
const path=require('path');
module.exports={
entry:'./src/index.js',
output:{
path:path.resolve(__dirname, './dist'),
filename:'bundle.js'
},
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
},
postcss:[
require('autoprefixer')({
broswers:['last 5 versions']
})
],
}
使用less和sass
npm install less-loader --save-dev
const path=require('path');
module.exports={
entry:'./src/index.js',
output:{
path:path.resolve(__dirname, './dist'),
filename:'bundle.js'
},
module:{
rules:[
{
test:/\.less$/,
use:[
'style-loader',
'css-loader',
'less-loader'
]
}
]
}
}
- 处理images
npm install --save-dev file-loader
- webpack.config.js中,module->rules添加节点
{
test:/\.(png|svg|jpg|gif)$/,
use:[
'file-loader'
]
}
- 处理fonts
{
test:/\.(woff|woff2|eot|ttf|otf)$/,
use:[
'file-loader'
]
}
- 处理数据
{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}
- 使用babel
安装npm install babel-loader babel-core babel-preset-env
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [require('@babel/plugin-transform-object-rest-spread')]
}
}
}
]
}
四、管理输出(Output Management)
- 最新的目录结构
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
|- print.js
|- /node_modules
- src目录下新建print.js
export default function printMe(){
console.log('I get called from print.js!');
}
- index.js修改
import printMe from './print'
function compoent(){
var ele=document.createElement('div');
var btn=document.createElement('button');
ele.innerHTML="Hello Webpack!";
btn.innerHTML='Click me and check the console!';
btn.onclick=printMe;
ele.appendChild(btn);
return ele;
}
document.body.appendChild(compoent());
- webpack.config.js
const path=require('path');
module.exports={
entry:{
app:'./src/index.js',
print:'./src/print.js'
},
output:{
path:path.resolve(__dirname, './dist'),
filename:'[name].bundle.js'
}
}
- 运行
npm run build
,会生成app.bundle.js
和`print.bundle.js
;
- 使用HtmlWebpackPlugin
可以使得html引用的文件自动跟随改变。
npm install --save-dev html-webpack-plugin
- webpack.config.js
const path=require('path');
const HtmlWebPackPlugin=require('html-webpack-plugin');
module.exports={
entry:{
app:'./src/index.js',
print:'./src/print.js'
},
plugins:[
new HtmlWebPackPlugin({
title:'Output Management'
})
],
output:{
path:path.resolve(__dirname, './dist'),
filename:'[name].bundle.js'
}
}
- 运行打包后,将会在dist文件夹下替换原来的index.html,并且title变为配置中的
Output Management
。
- 根据html模板生成html
- 在根目录新建index.html,用ejs的写法对title传参
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</html>
- webpack.config.js
plugins:[
new HtmlWebPackPlugin({
template:'index.html',
inject:'head',//默认body,false则不自动插入
filename:'index-[hash].html',
title:'webpack is good!'
// minify:{压缩
// removeComments:true,删除注释
// collapseWhitespace:true 删除空格
// }
})
],
- 运行打包后将生成index.html,自动插入两个ouput文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webpack is good!</title>
<script type="text/javascript" src="app.bundle.js"></script><script type="text/javascript" src="print.bundle.js"></script></head>
<body>
</html>
- 清除/dist目录
- 安装 clean-webpack-plugin
npm install clean-webpack-plugin --save-dev
- webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
+ new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
五、开发
- source-map定位出错的代码,webpack.config.js
entry:{
app:'./src/index.js',
print:'./src/print.js'
},
+ devtool:'inline-source-map',
- 观察模式
1)可以用--watch
参数,文件改动后将自动运行打包。
2) webpack-dev-server
在文件改动后,实现浏览器自动刷新
npm install --save-dev webpack-dev-server
3) 修改webpack.config.js,告诉开发服务器在哪找文件
devtool: 'inline-source-map',
+ devServer: {
+ contentBase: './dist'
+ },
4) 配置的意思是告诉webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。在package.json添加start命令,如下:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --progress --watch",
"start": "webpack-dev-server --open",
"build": "webpack"
},
5) 运行npm start
即可开启服务。
- webpack-dev-middleware,是一个中间件容器(wrapper),它将通过 webpack 处理后的文件发布到一个服务器(server),配合express使用。
六、模块热更新
- 启用webpack内置的HMR,修改webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const webpack = require('webpack');
module.exports = {
entry: {
- app: './src/index.js',
- print: './src/print.js'
+ app: './src/index.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
+ hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
+ new webpack.NamedModulesPlugin(),
+ new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
七、Tree Shaking移除未引用的代码
针对使用 ES2015 模块语法(即 import 和 export)才能精简,运行后会压缩。
- 安装
npm i --save-dev uglifyjs-webpack-plugin
- 修改配置webpack.config.js
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new UglifyJSPlugin()
]
};
八、生产环境构建
目标是:更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。
- 配置
- 生产环境与开发环境的配置分开,同时保留通用的配置。用 webpack-merge,安装:
npm install --save-dev webpack-merge
- 删掉webpack.config.js,新建webpack.common.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Production'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
- webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});
- webpack.prod.js
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
plugins: [
new UglifyJSPlugin()
]
});
- npm scripts
修改package.json,npm run build指定为生产环境,npm start指定为生产环境
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.prod.js",
"watch": "webpack --watch",
"start": "webpack-dev-server --open --config webpack.dev.js"
},
- minification
- 压缩代码方面,除了UglifyJSPlugin,还可以选择BabelMinifyWebpackPlugin,ClosureCompilerPlugin这两者。
- source map
开发环境中可以用inline-source-map,但生产环境要这样用
- webpack.prod.js
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
+ devtool: 'source-map',
plugins: [
- new UglifyJSPlugin()
+ new UglifyJSPlugin({
+ sourceMap: true
+ })
]
})
- 指定环境
通过指定process.env.NODE_ENV === 'production'来指定生产环境,在有些时候可以区分dev和prod
- webpack.prod.js
+ const webpack = require('webpack');
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
devtool: 'cheap-module-source-map',
plugins: [
new UglifyJSPlugin({
sourceMap: true
}),
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ })
]
})
在开发时可以这样用来调试,src/index.js
import { cube } from './math.js';
+
+ if (process.env.NODE_ENV !== 'production') {
+ console.log('Looks like we are in development mode!');
+ }
function component() {
var element = document.createElement('pre');
element.innerHTML = [
'Hello webpack!',
'5 cubed is equal to ' + cube(5)
].join('\n\n');
return element;
}
document.body.appendChild(component());
- cli替代选项
在命令中添加参数也可以起到配置的作用
1) 参数--optimize-minimize
表示引用UglifyJSPlugin
插件来打包。
2) --define process.env.NODE_ENV="'production'"
起到上面DefinePlugin的作用。
3) webpack -p
将同时使用上面两项。
九、代码分离
- 入口起点
- 防止重复
使用 CommonsChunkPlugin分离公共的模块,生成common.bundle.js
- webpack.config.js
const path = require('path');
+ const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
- })
+ }),
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'common' // 指定公共 bundle 的名称。
+ })
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
其他代码分离的插件:
- ExtractTextPlugin: 用于将 CSS 从主应用程序中分离。
- bundle-loader: 用于分离代码和延迟加载生成的 bundle。
- promise-loader: 类似于 bundle-loader ,但是使用的是 promises。
- CommonsChunkPlugin 插件还可以通过使用显式的 vendor chunks 功能,从应用程序代码中分离 vendor 模块。
- 动态导入
- bundle分析
- 下一步
十、懒加载
- 示例
- 框架
十一、缓存
- 输出文件的文件名
- chunkhash表示在文件内容不变的情况下,hash不变。
- hash表示每次打包都会生成一个不同的文件名。
output: {
- filename: 'bundle.js',
+ filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
- 提取模板
- CommonsChunkPlugin 可以用于将模块分离到单独的文件中,将 webpack 的样板(boilerplate)和 manifest 提取出来.webpack.config.js
const path = require('path');
+ const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
- })
+ }),
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'runtime'
+ })
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
打包时将生成一个runtime.bundle.js。
- 将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。这可以通过使用新的 entry(入口) 起点,以及再额外配置一个 CommonsChunkPlugin 实例的组合方式来实现:
var path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
- entry: './src/index.js',
+ entry: {
+ main: './src/index.js',
+ vendor: [
+ 'lodash'
+ ]
+ },
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor'
+ }),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
将生成一个vendor bundle。
注意,引入顺序在这里很重要。CommonsChunkPlugin 的 'vendor' 实例,必须在 'runtime' 实例之前引入。
- 模块标识符
- bundle 会随着自身的新增内容的修改,而发生变化。
vendor bundle 会随着自身的 module.id 的修改,而发生变化。
runtime bundle 会因为当前包含一个新模块的引用,而发生变化。
第一个和最后一个都是符合预期的行为 -- 而 vendor 的 hash 发生变化是我们要修复的。幸运的是,可以使用两个插件来解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建:
十二、创建Library
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Caching'
}),
+ new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime'
})
],
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
现在,不管再添加任何新的本地依赖,对于每次构建,vendor hash 都应该保持一致:
十三、创建Library
webpack可以创建一个library,详情看文档。
十四、Shimming
十五、TypeScript
十六、迁移到新版本
十七、使用环境变量
十八、构建性能
- 保持版本最新
- Loaders,使用 include 字段仅将 loader 模块应用在实际需要用其转换的位置中:
{
test: /\.js$/,
include: path.resolve(__dirname, "src"),
loader: "babel-loader"
}
- 解析
- 尽量减少 resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles 中类目的数量,因为他们会增加文件系统调用的次数。
- 如果你不使用 symlinks ,可以设置 resolve.symlinks: false (例如 npm link 或者 yarn link).
- 如果你使用自定义解析 plugins ,并且没有指定 context 信息,可以设置 resolve.cacheWithContext: false 。
- Dlls
使用 DllPlugin 将更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即使它增加了构建过程的复杂性。 - Smaller = Faster
减少编译的整体大小,以提高构建性能。尽量保持 chunks 小巧。
- 使用 更少/更小 的库。
- 在多页面应用程序中使用 CommonsChunksPlugin。
- 在多页面应用程序中以 async 模式使用 CommonsChunksPlugin 。
- 移除不使用的代码。
- 只编译你当前正在开发部分的代码。
- Worker Pool
thread-loader 可以将非常消耗资源的 loaders 转存到 worker pool 中。 - 持久化缓存
使用 cache-loader 启用持久化缓存。使用 package.json 中的 "postinstall" 清除缓存目录。 - 自定义 plugins/loaders
- Development环境推荐
9.1 增量编译
使用 webpack 的监听模式。不要使用其他工具来监听你的文件和调用 webpack 。在监听模式下构建会记录时间戳并将信息传递给编译让缓存失效。
在某些设置中,监听会回退到轮询模式。有许多监听文件会导致 CPU 大量负载。在这些情况下,你可以使用 watchOptions.poll 来增加轮询的间隔。
9.2 在内存中编译
以下几个实用工具通过在内存中进行代码的编译和资源的提供,但并不写入磁盘来提高性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
9.3 Devtool
在大多数情况下,cheap-module-eval-source-map 是最好的选择。
9.4 避免在生产环境下才会用到的工具
某些实用工具, plugins 和 loaders 都只能在构建生产环境时才有用。例如,在开发时使用 UglifyJsPlugin 来压缩和修改代码是没有意义的。以下这些工具在开发中通常被排除在外:
UglifyJsPlugin
ExtractTextPlugin
[hash]/[chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
9.5 最小化入口 chunk
- Production推荐的配置
10.1 多个编译时
parallel-webpack: 它允许编译工作在 worker 池中进行。
cache-loader: 缓存可以在多个编译时之间共享。
10.2 少用source maps
十九、开发Vagrant
二十、管理依赖
二十一、publicPath
二十二、集成
「一键投喂 软糖/蛋糕/布丁/牛奶/冰阔乐!」
(๑>ڡ<)☆谢谢老板~
使用微信扫描二维码完成支付