代码:githubjavascript
mkdir webpack4 cd webpack4 mkdir demo1 cd demo1 npm init -y 或 npm init
webpack4 ├── webpack4/demo1 │ └── webpack4/demo1/package.json └── webpack4/README.md
npm install webpack --save-dev
css
npm install --save-dev webpack@<version>
html
npm install webpack-cli --save-dev
java
npx webpack -v
:查看webpack版本npx webpack-cli -v
:查看webpack-cli版本推荐本地安装webpack和webpack-cli
写这篇博客的时候webpack最新版本为:4.30.0,也是这篇学习webpack4使用的版本
在demo1目录下新建src目录,在src目录下新建index.jsnode
mkdir src cd src touch index.js
demo1目录结构webpack
demo1 ├── demo1/package.json ├── demo1/package-lock.json └── demo1/src └── demo1/src/index.js
在index.js中加写代码,例如:git
//index.js let demo='webpack4' console.log(demo)
webpack4能够零配置打包,webpack4会默认对src目录下的index.js文件打包。
如今运行npx webapck
,能够在demo1目录下看到dist目录,dist目录下有一个main.js文件,这就是打包后的文件,打开查找能够看到 console.log(demo)
,说明index.js被打包到main.js中。github
在demo1
目录下新建webpack
配置文件webpack.config.js
web
webpack
--webpack.config.jsconst path = require('path') module.exports={ //mode development: 开发环境 production:生产环境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成后文件输出位置配置 output: { //filename 设置打包后文件的名字 //若是不设置filename,则文件的名字跟入口文件路径的属性名同样 filename: 'bundle.js', //path 设置打包完成后文件输出路径 path: path.resolve(__dirname,'dist') } }
运行npx webpack
命令npx webpack
等价于npx webpack --config webpack.config.js
chrome
当webapck
配置文件命名为webpack.config.js
时能够省略--config *.js
,直接执行npx webpack
便可,不然执行npx webpack --config 配置文件名
。
看到dist目录下有bundle.js
,说明webpack配置正确。
在package.json
中配置'script'
"scripts": { "build": "webpack" }
添加"build": "webpack"
,运行npm run build
效果等价于执行npx webpack
命令。
file-loader
的使用安装file-loader
npm i file-loader --save-dev
const path = require('path') module.exports={ //mode development: 开发环境 production:生产环境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成后文件输出位置配置 output: { //filename 设置打包后文件的名字 //若是不设置filename,则文件的名字跟入口文件路径的属性名同样 filename: 'bundle.js', //path 设置打包完成后文件输出路径 path: path.resolve(__dirname,'dist') }, module: { rules:[ { test: /\.(png|jpg|gif)$/, use: { loader: 'file-loader', options: { name: '[name].[ext]', //对打包后的图片命名 outputPath: 'images/' //打包后图片输出的位置 dist\images } } } ] } }
在src目录下新建images文件夹,存放图片
//index.js //import导入图片 import image from './images/11.png' let img=new Image() img.src=image document.body.append(img)
运行npm run build
后的目录结构以下
demo1 ├── demo1/dist │ ├── demo1/dist/bundle.js │ ├── demo1/dist/images │ │ └── demo1/dist/images/11.png │ └── demo1/dist/index.html ├── demo1/package.json ├── demo1/package-lock.json ├── demo1/src │ ├── demo1/src/images │ │ └── demo1/src/images/11.png │ └── demo1/src/index.js └── demo1/webpack.config.js
在dist目录下出现了images目录和图片,建立index.html,引入js文件,在浏览器中打开就能够看到图片。
url-loader
的使用url-loader
安装
npm i url-loader -D
url-loader
的做用跟'file-loader'的做用很相似
module: { rules:[ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', //对打包后的图片命名 outputPath: 'images/', //打包后图片放的位置 dist\images limit: 20480 //1024 == 1kb //小于20kb时打包成base64编码的图片不然单独打包成图片 } } } ] } }
limit
属性:当图片大小大于属性值时打包成图片输出到images目录下,不然打包成base64编码的图片注入bundle.js中
由于base64编码的图片致使打包文件变大,因此图片比较小时打包成base64编码的图片,图片比较大时单独打包成一张图片。
css
和scss
的打包安装相应的loader
npm i css-loader style-loader -D
npm i node-sass sass-loader -D
npm i postcss-loader -D
npm i autoprefixer -D
postcss-loader
和autoprefixer
配合使用能够在打包过程当中自动添加前缀
在demo1根目录下新建postcss.config.js
,配置以下
//postcss.config.js module.exports={ plugins: [ require('autoprefixer') ] }
module:{ rules:[ { test: /\.css$/, use:[ 'style-loader', 'css-loader', 'postcss-loader' //加前缀 npm i autoprefixer -D //在项目根目录下配置postcss.config.js文件 ] }, { test: /\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, //importLoaders //用于配置css-loader做用于@import的资源以前有多少个loader先做用于@import的资源 } }, 'postcss-loader', 'sass-loader' ] } ] }
在demo1
的src
下新建css
文件夹,在css
文件夹下新建style.css
和index.scss
文件。
index.scss
body{ border: 1px solid red; width: 300px; height: 300px; img{ width: 100px; height: 100px; border-radius: 10%; transform: translate(100px,100px); } }
style.css
body{ border-radius: 10%; }
index.js
//index.js import image from './images/11.png' import './style.css' import './index.scss' let img=new Image() img.src=image document.body.append(img)
运行npm run build
,在dist目录下新建index.html,引入js文件,在浏览器中打开就能够看到效果,说明打包成功。
css模块化,避免页面样式之间相互影响
在webpack.config.js
中的css-loader
添加modules: true
//webpack.config.js module:{ rules: [ { test: /\.css$/, use:[ 'style-loader', { loader: 'css-loader', options: { modules: true } }, 'postcss-loader' //加前缀 npm i autoprefixer -D //在项目根目录下配置postcss.config.js文件 ] }, { test: /\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, //importLoaders //用于配置css-loader做用于@import的资源以前有多少个loader先做用于@import的资源 modules: true //加载css模块化打包,避免样式文件之间相互影响 } }, 'postcss-loader', 'sass-loader' ] } ] }
修改index.js.img
是类名须要在样式文件中提早写好样式
//index.js import image from './images/11.png' import style from './css/style.css' // import style from './css/index.scss' let img=new Image() img.src=image //style.img .img是scss文件中写好的类名 img.classList.add(style.img) document.body.append(img)
index.scss
body{ border: 1px solid red; width: 300px; height: 300px; img{ width: 100px; height: 100px; border-radius: 10%; transform: translate(100px,100px); } .img{ border: 10px solid royalblue; } }
style.css
body{ border-radius: 10%; } body .img{ border: 10px solid yellow; }
结果
能够看到添加了一个class,类名是一串比较复杂的字符串,从而避免这个样式对别的元素产生影响。
这一部分主要是学会使用html-webpack-plugin
和clean-webpack-plugin
插件,主要是学会配置devServer
以及使用webpack的热模块替换功能。
首先,在webpack4
目录下新建demo2
文件夹将demo1
目录下的全部东西复制到demo2
中
在上一部分咱们都是手动在dist目录下建立index.html引入js文件查看打包结果,这样会很麻烦。咱们可使用html-webpack-plugin
来自动生产index.html,而且可以自动引入打包好的文件,直接打开生产的html就能够看到打包结构。
html-webpack-plugin
的使用安装
npm i html-webpack-plugin -D
在webpack.config.js
中配置plugins配置项
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') module.exports={ //mode development: 开发环境 production:生产环境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成后文件输出位置配置 output: { //filename 设置打包后文件的名字 //若是不设置filename,则文件的名字跟入口文件路径的属性名同样 filename: 'bundle.js', //path 设置打包完成后文件输出路径 path: path.resolve(__dirname,'dist') }, module: { }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }) ] }
在demo2目录下新建index.html做为模板
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>模板</title> </head> <body> <div id="root"></div> <script type="text/javascript" src="bundle.js"></script></body> </html>
运行npm run build
,能够看到dist目录下自动生产index.html,而且还自动引入js文件
clean-webpack-plugin
的使用每次打包生成的dist目录,若是改一次代码,都得要删除一次dist目录,这样很麻烦,能够经过clean-webpack-plugin
在每次打包的前自动清空dist目录。
安装
npm i clean-webpack-plugin -D
在webpack.config.js
的plugins
中配置以下
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports={ //mode development: 开发环境 production:生产环境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js' }, //打包完成后文件输出位置配置 output: { //filename 设置打包后文件的名字 //若是不设置filename,则文件的名字跟入口文件路径的属性名同样 filename: 'bundle.js', //path 设置打包完成后文件输出路径 path: path.resolve(__dirname,'dist') }, module: { }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }), new cleanWebpackPlugin() ] }
运行npm run build
,能够本身测试,每次打包前都会把dist目录下的文件删掉。
entry
和output
多入口配置module.exports={ //mode development: 开发环境 production:生产环境 mode: 'development', //entry 入口文件配置 entry: { index: './src/index.js', main: './src/index.js' }, //打包完成后文件输出位置配置 output: { //filename 设置打包后文件的名字 //若是不设置filename,则文件的名字跟入口文件路径的属性名同样 // 占位符 filename: '[name].js', //path 设置打包完成后文件输出路径 path: path.resolve(__dirname,'dist') }, }
当有多入口的时候,须要修改filename
的属性值为'[name].js'
运行npm run build
,就会在dist目录下生产index.js
和main.js
devtool
devtool决定源代码与打包后的代码之间的映射关系,方便对代码进行调试。
开发环境推荐: cheap-module-eval-source-map
生产环境推荐: cheap-module-source-map
devtool具体内容请查阅:文档:devtool
module.exports={ devtool: 'cheap-module-eval-source-map', //开发环境推荐: cheap-module-eval-source-map //生产环境推荐: cheap-module-source-map }
devServer
安装webpack-dev-server
npm i webpack-dev-server -D
module.exports={ devServer: { contentBase: './dist', // open: true, //自动打开浏览器 // port: 8080, //默认8080 } }
修改package.json
的script
,添加 "start": "webpack-dev-server"
"scripts": { "start": "webpack-dev-server" },
执行npm run start
后打开浏览器就能够看到效果,当咱们修改代码的时候页面就会从新刷新。
有时咱们但愿页面只更新咱们修改的那一部分就能够了,而并非刷新页面,因此须要启用webpack的热模块替换功能。
import './css/style.css' var btn = document.createElement('button') btn.innerHTML='新增' document.body.appendChild(btn) btn.onclick=function(){ var div=document.createElement('div') div.innerHTML='items' document.body.appendChild(div) }
//style.css body{ background: yellow; } div:nth-of-type(odd){ background: chartreuse; font-size: 18px; }
引入webpack:const webpack=require('webpack')
添加内容以下:
const webpack=require('webpack') module.exports={ plugins: [ new webpack.HotModuleReplacementPlugin() //启用HMR ], devServer: { contentBase: './dist', // open: true, //自动打开浏览器 // port: 8080, hot: true, //启用webpack的热模块替换功能 hotOnly: true //devServer.hot在没有页面刷新的状况下启用热模块替换做为构建失败时的后备 } }
hot:true
启用HotModuleReplacementPlugin
(HMR)
执行npm run start
,在浏览器打开之后,修改div的背景颜色,只有改变的地方才发生变化,可是页面并无刷新。
在demo2的src目录下新建number.js
var number=function(){ var div=document.createElement('div') div.setAttribute("id","number") div.innerHTML=103 document.body.appendChild(div) } export default number
修改index.js
import number from './number' number()
运行npm run start
,在浏览器中打开看结果,而后在number.js中修改内容,可是页面并无显示修改后的内容
这是由于在引入js文件的时候,热模块替换的实现方式有点区别。
js要达到热模块替换的效果,得要if(module.hot){}这一部分代码,不然就算改了代码,页面不刷新,修改的地方在页面上页面变化。
css样式由于css-loader已经实现if(module.hot){}这一部分,因此不须要单独实现这一部分。
再次修改index.js
import number from './number' number() if(module.hot){ module.hot.accept('./number.js',function(){ number() document.body.removeChild(document.getElementById('number')) }) }
运行npm run start
,在浏览器中打开看结果,而后在number.js中修改内容,发现页面显示修改后的内容
Babel是一个普遍使用的转码器,能够将ES6代码转为ES5代码,从而在现有环境执行。
Babel总共分为三个阶段:解析(parse),转换(transform),生成(generate)。
Babel自己不具备任何转化功能,它把转化的功能都分解到一个个plugin
里面。所以当咱们不配置任何插件时,通过Babel
输出的代码和输入是相同的。
Babel插件的使用
.babelrc
配置文件或是webapck.config.js
中配置,通常都是在.babelrc
中配置。Babel的配置文件是.babelrc
,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。
该文件用来设置转码规则和插件,基本格式以下。
{ "presets": [], "plugins": [] }
preset
preset(预设)就是一系列插件的集合@babel/preset-env
包含全部ES6转译为ES5的插件集合
core-js
转换一些内置类(Promise, Symbols等等)和静态方法(Array.from等)。
@babel/core
是做为Babel的核心存在,Babel的核心api都在这个模块里面。
babel-loader
babel-loader
在webpack中使用,是webpack和Babel之间的通信桥梁
@babel/polyfill介绍
@babel/preset-env
默认只转译js
语法,而不转译新的API
,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise
等全局对象,以及一些定义在全局对象上的方法(好比Object.assign
)都不会转译。这时就必须使用@babel/polyfill
(内部集成了core-js
和regenerator
)。
使用时,在全部代码运行以前增长import "@babel/polyfill"
或者是在webpack.config.js
入口配置
module.exports = { entry: ["@babel/polyfill", "./app/js"], }
所以必须把@babel/polyfill
做为dependencies
而不是devDependencies
@babel/polyfill主要有两个缺点:
1.使用@babel/polyfill
须要作些额外配置,实现打包的时候按需引入,不然会把@babel/polyfill
所有注入代码中会致使打出来的包很是大。
2.@babel/polyfill
会污染全局变量。
Babel7
的一个重大变化就是npm package
名称的变化,把全部babel-*
重命名为@babel/*
,例如:
babel-polyfill
重命名为@babel/polyfill
babel-preset-env
重命名为@babel/preset-env
首先实现对ES6语法的转译
在webpack4目录下新建demo3文件夹,将demo2目录下的全部东西复制到demo3中
安装babel-loader、 @babel/core、@babel/preset-env
npm i babel-loader -D
npm i @babel/core -D
npm i @babel/preset-env -D
babel-loader@8
须要安装@babel/core7.x
版本。
在webpack.config.js配置
module.exports={ module: { rules:[ { test: /\.js$/, exclude: /node_modules/, use:{ loader: 'babel-loader', options:{ presets: [ ["@babel/preset-env",{ //targets:表示编译出的代码想要支持的浏览器版本 targets: { chrome: "67" } }] ] } } } ] } }
执行npm run build
或npx webpack
就能够看到dist目录下的打包文件,可是只是将ES6的语法进行转译,并无对ES6新API进行转译,因此咱们须要配置@babel/polyfill
解决这个问题。
安装@babel/polyfill
npm i @babel/polyfill --save
在index.js
中引入@babel/polyfill
index.js
//index.js import '@babel/polyfill' let arr=[ new Promise(()=>{}), new Promise(()=>{}), 2 ] arr.map((item)=>{ console.log(item) })
引入@babel/polyfill
前,main.js的大小为29.5KB
引入@babel/polyfill
后,main.js的大小为1MB
注意:以上对比都是在没有targets
这个选项的状况下,由于有些浏览器几乎都支持ES6,在这种状况下,@babel/preset-env
将不会对代码进行处理。
这是由于把@babel/polyfill
对全部API的实现都注入到打包文件中,可是里面不少的API咱们在代码中并无用到,因此须要修改配置,按需引入对应的API。
修改webpack.config.js配置
添加"useBuiltIns": "usage"
之后,须要安装core-js@2
,而且添加"corejs": 2
配置项,这时配置选项比较多,须要在项目根目录下新建.babelrc
文件,在这个文件中配置。
.babelrc
配置以下:
"useBuiltIns"
属性值为"usage"
时,会自动引入@babel/polyfill
,必须保证已经安装了@babel/polyfill
"useBuiltIns"
属性值为"usage"
时,须要添加"corejs": 2
配置项,不然报错,须要安装core-js
首先删掉index.js
中的import '@babel/polyfill'
安装core-js
npm i --save core-js@2
或npm i --save core-js@3
{ "presets": [["@babel/preset-env",{ "useBuiltIns": "usage", //不须要把polly都打包到代码中,根据代码按需转译 // core-js@3和core-js@2二选一 //"corejs": 3, //npm i --save core-js@3 "corejs": 2 //npm i --save core-js@2 }]] }
修改webpack.config.js
,删除options
对象
module.exports={ module: { rules:[ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] } }
执行npm run build
,打包后的文件大小为165KB
可是,在开发类库或是第三方模块时不适合使用@babel/polyfill
,因此接下来使用@babel/plugin-transform-runtime
来解决这个问题。
@babel/plugin-transform-runtime、@babel/runtime和@babel/runtime-corejs2的用法
@babel/runtime-corejs2:是一个包含Babel modular runtime helpers
和regenerator-runtime
以及core-js
的库。
@babel/runtime:是一个包含Babel modular runtime helpers
和regenerator-runtime
的库。
在配置项中corejs
属性值为默认为false
,若是须要将Promise
等API
进行转译,则须要设置属性值为2
时,而且安装@babel/runtime-corejs2
安装:
npm i @babel/plugin-transform-runtime -D
npm i --save @babel/runtime
npm i --save @babel/runtime-corejs2
修改.babelrc文件
{ "plugins": [ ["@babel/plugin-transform-runtime",{ "helpers": true, "regenerator": true, "useESModules": false, "corejs": 2 }] ] }
咱们把presets
配置项去掉了,而后npm run build
打包,打开打包后的main.js
查看,虽然把转译了Promise
,可是ES6新语法并没被转译,例如:let
没有被转译为var
。
因此仍是须要配置presets
,由于"@babel/preset-env"
包含了对全部ES6语法转译为ES5插件。
再次修改.babelrc文件
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-transform-runtime",{ "helpers": true, "regenerator": true, "useESModules": false, "corejs": 2 }] ] }
添加presets
配置项,而后npm run build
打包,打开打包后的main.js
查看,能够看到let
和箭头函数都被转译为ES5语法了。
Tree Shaking
使用首先,在webpack4目录下新建demo4文件夹,将demo3目录下的全部东西复制到demo4中
Tree Shaking
能够用来剔除JavaScript
中用不上的死代码。它依赖静态的ES6
模块化语法,例如经过import
和export
导入导出。
须要注意的是要让Tree Shaking
正常工做的前提是JavaScript
代码必须采用ES6
模块化语法,由于ES6
模块化语法是静态的,这让Webpack
能够简单的分析出哪些export
的被import
过了。
接下来配置Webpack
让Tree Shaking
生效
webpack4
默认保留ES6模块化语句,并无经过Babel将其转换
修改.babelrc
文件为以下:
//.babelrc { "presets": [["@babel/preset-env",{ "useBuiltIns": "usage", "corejs": 2, "modules":false //关闭 Babel 的模块转换功能,保留本来的 ES6 模块化语法 //默认是auto,取值还能够是 amd, umd, systemjs, commonjs,auto等 }]] }
修改webapck.config.js
,添加
optimization: { usedExports: true }
到module.exports{}
中
module.exports={ mode: 'development', optimization: { //开发坏境使用tree shaking时加usedExports: true usedExports: true }, }
还需经过package.json
的"sideEffects"
属性来告诉webpack哪些模块是能够忽略掉,若是没有则设置为false
,来告知webpack,它能够安全地删除未用到的export
。
修改package.json
{ "name": "your-project", "sideEffects": false }
在demo4下的src新建math.js
index.js
//tree shaking import export import {cube} from './math.js' let component = () => { let element = document.createElement('pre') element.innerHTML = [ 'Hello webpack!', '2 cubed is equal to ' + cube(2) ].join('\n\n'); console.log(cube) return element; } document.body.appendChild(component());
math.js
export let square= (x) => { console.log(x) return x * x; } export let cube = (x) => { console.log(x) return x * x * x; }
运行npm run build
,而后打开打包后的js文件:main.js找到下面这段文字
/*!*********************!*\ !*** ./src/math.js ***! \*********************/ /*! exports provided: square, cube */ /*! exports used: cube */ /***/
从上面这段文字能够看出Tree Shaking
生效了,可是在开发环境下,并无把没有用的代码删掉,由于在开发环境下还须要对代码进行调试。
咱们已经找出须要删除的“未引用代码(dead code)”,然而,不只仅是要找出,还要删除它们。为此,咱们须要将mode
配置选项设置为production
,将optimization对象删掉,修改devtool
配置选项
webpack.config.js
module.exports = { mode: 'production', devtool: 'cheap-module-source-map' }
运行npm run build
,查看打包结果就能够看到没有用的代码被删掉了。
Develoment
和Production
不一样环境的配置在webpack4下新建demo5,将demo4下的全部文件复制到demo5中
由于在不一样的环境下,webpack的配置稍微有点区别,若是咱们须要在不一样的换将下切换,就得修改webpack配置,这是很麻烦并且还容易改错,因此咱们须要把配置文件进行拆分。
在项目根目录下新建build文件夹,而后在build文件夹中新建webpack.dev.js
、webpack.prod.js
和webpack.base.js
三个文件
webpack.dev.js
:是开发环境webpack.prod.js
:是生产环境webpack.base.js
:是开发环境和生产环境都用到的配置
这几个文件之间的结合靠'webpack-merge'这个插件。
安装
npm i webpack-merge -D
webpack.dev.js
//webpack.dev.js const webpack=require('webpack') const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const devConfig={ mode: 'development', devtool: 'cheap-module-eval-source-map', plugins: [ new webpack.HotModuleReplacementPlugin() ], optimization: { usedExports: true }, devServer: { contentBase: './dist', // open: true, //自动打开浏览器 // port: 8080, hot: true, //启用webpack的热模块替换功能 //hotOnly: true //devServer.hot在没有页面刷新的状况下启用热模块替换做为构建失败时的后备 } } module.exports=merge(baseConfig,devConfig)
webapck.prod.js
//webapck.prod.js const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const prodConfig={ mode: 'production', devtool: 'cheap-module-source-map' } module.exports=merge(baseConfig,prodConfig)
可是这两个文件还有大量重复的代码,新建webpack.base.js
//webpack.base.js const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports={ entry: { main: './src/index.js' }, output: { filename: '[name].js', path: path.resolve(__dirname,'dist') }, module: { rules:[ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', outputPath: 'images/', limit: 2048 } } }, { test: /\.css$/, use:[ 'style-loader', 'css-loader', 'postcss-loader' ] }, { test: /\.scss$/, use:[ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, modules: true } }, 'sass-loader', 'postcss-loader' ] }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }), new cleanWebpackPlugin(), ] }
修改package.json
的script
:
{ "scripts": { "dev": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js" }, }
开发环境:运行npm run dev
,打开浏览器访问http://localhost:8080/
就能够看到结果
生产环境:运行npm run build
webpack 4
移除CommonsChunkPlugin
,取而代之的是SplitChunksPlugin
。下面介绍SplitChunksPlugin
的用法。
在webpack4目录
下新建demo6目录
,将demo5目录
下的全部文件都复制到demo6
中。
安装 lodash
npm i lodash --save
在package.json
添加
"scripts": { "dev-build": "webpack --config ./build/webpack.dev.js" }
修改index.js
import _ from 'lodash' console.log(_.join(['lodash', 'babel', 'webpack'], '-'))
运行npm run dev-build
后,demo6目录结构以下
demo6 ├── demo6/build │ ├── demo6/build/webpack.base.js │ ├── demo6/build/webpack.dev.js │ └── demo6/build/webpack.prod.js ├── demo6/dist │ ├── demo6/dist/index.html │ └── demo6/dist/main.js ├── demo6/index.html ├── demo6/package.json ├── demo6/package-lock.json ├── demo6/postcss.config.js └── demo6/src └── demo6/src/index.js
dist目录下只有main.js一个js文件,lodash库也一块儿打包到main.js里面,这种状况下会致使文件变大,致使页面加载速度变慢,咱们须要把第三库或是须要单独打包的代码给分割出来。
这时须要使用webpack4自带的插件SplitChunksPlugin
,默认状况下它将只会影响按需加载的代码块
在webpack.config.js
添加optimization.splitChunks.chunks
optimization: { splitChunks: { //chunks: all, async, initial. //async针对异步加载的chunk作切割,initial针对初始chunk,all针对全部chunk。 chunks: 'async' } }
运行npm run dev-build
后,打包后的代码并无分割。
修改optimization.splitChunks.chunks
为all
optimization: { splitChunks: { //chunks: all, async, initial. //async针对异步加载的chunk作切割,initial针对初始chunk,all针对全部chunk。 chunks: 'all' } }
运行npm run dev-build
后,demo6目录结构以下
demo6 ├── demo6/build │ ├── demo6/build/webpack.base.js │ ├── demo6/build/webpack.dev.js │ └── demo6/build/webpack.prod.js ├── demo6/dist │ ├── demo6/dist/index.html │ ├── demo6/dist/main.js │ └── demo6/dist/vendors~main.js ├── demo6/index.html ├── demo6/package.json ├── demo6/package-lock.json ├── demo6/postcss.config.js └── demo6/src └── demo6/src/index.js
能够看到dist目录下多了vendors~main.js
文件,说明SplitChunksPlugin
插件生效了
接下来先看optimization.splitChunks
的默认配置
module.exports = { //... optimization: { splitChunks: { chunks: 'async', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } }
chunk
,共享该模块的chunk必须不小于1时才分割从以上能够看出来,默认的配置只对异步加载的模块有效
修改index.js,异步加载lodash
function getComponent(){ return import('lodash').then(({default: _})=>{ var element=document.createElement('div') element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-') return element }) } getComponent().then(element=>{ document.body.appendChild(element) })
这时运行npm run dev-build
会报错,须要下载安装 @babel/plugin-syntax-dynamic-import
npm i @babel/plugin-syntax-dynamic-import -D
在.babelrc中添加
"plugins": ["@babel/plugin-syntax-dynamic-import"]
再次运行npm run dev-build
,此时打包成功,目录结构以下
demo6 ├── demo6/build │ ├── demo6/build/webpack.base.js │ ├── demo6/build/webpack.dev.js │ └── demo6/build/webpack.prod.js ├── demo6/dist │ ├── demo6/dist/0.js │ ├── demo6/dist/index.html │ └── demo6/dist/main.js ├── demo6/index.html ├── demo6/package.json ├── demo6/package-lock.json ├── demo6/postcss.config.js └── demo6/src └── demo6/src/index.js
能够看到dist目录下0.js
,就是对lodash打包后的文件,有时咱们但愿可以改变0.js
的名字
修改index.js,添加/* webpackChunkName:"lodash" */
function getComponent(){ return import(/* webpackChunkName:"lodash" */'lodash').then(({default: _})=>{ var element=document.createElement('div') element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-') return element }) } getComponent().then(element=>{ document.body.appendChild(element) })
运行npm run dev-build
,发现0.js
变为vendors~lodash.js
也能够经过设置optimization.splitChunks.cacheGroups.vendors.name
来修改打包后的文件名字
修改optimization.splitChunks
配置
optimization: { splitChunks: { chunks: 'all', minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10, name: 'vendors' }, default: { minChunks: 1, priority: -20, reuseExistingChunk: true } } } },
运行npm run dev-build
,发现打包后的文件名字变为vendors.js
以上就是SplitChunksPlugin
的简单用法
MiniCssExtractPlugin
插件将CSS提取到单独的文件中。它为每一个包含CSS的JS文件建立一个CSS文件,在webpack 4才能使用
在webpack目录下新建demo7,将demo6下的全部文件都复制到demo7中,进入demo7
安装
npm install --save-dev mini-css-extract-plugin
官网提示,这个插件应该在生产环境中使用,因此修改webpack的相关配置。
首先将webpack.base.js
中对css和scss处理的loader配置分别复制粘贴(在webpack.base.js中删掉这一部分)到webpack.prod.js
和webpack.dev.js
中。
在webpack.prod.js
中引入const miniCssExtractPlugin = require('mini-css-extract-plugin')
修改后的相关配置以下:
const path = require('path') const htmlWebpackPlugin = require('html-webpack-plugin') const cleanWebpackPlugin = require('clean-webpack-plugin') module.exports = { entry: { main: './src/index.js' //对应filename //入口文件引入的模块,分割打包的名字对应chunkFilename }, output: { filename: '[name].js', chunkFilename: '[name].chunk.js', path: path.resolve(__dirname, '../dist') }, module: { rules: [ { test: /\.(png|jpg|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', outputPath: 'images/', limit: 2048 } } }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] }, plugins: [ new htmlWebpackPlugin({ template: './index.html' }), new cleanWebpackPlugin(), ], optimization: { usedExports: true, splitChunks: { //chunks: all, async, initial. //async 只对异步分割有效 // initial 同步 //all 要配置cacheGroups chunks: 'all', // 引入的包或是库大于30kb才对代码进行分割 minSize: 30000, maxSize: 0, //没多大意义 minChunks: 1, //当一个模块至少引入多少次时才会进行代码分割 maxAsyncRequests: 5, //同时加载的模块数最可能是5个 maxInitialRequests: 3, automaticNameDelimiter: '~', //打包后的文件名字之间的链接符 name: true, cacheGroups: { //缓存组 // vendors: false, vendors: { //同步 检查是否在node_modules里面 test: /[\\/]node_modules[\\/]/, priority: -10, //优先级 name: 'vendors' }, // default: false default: { minChunks: 1, priority: -20, reuseExistingChunk: true, //若是模块已经打包过了就引用以前打包好的模块 // filename: 'common.js' } } } } }
const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const miniCssExtractPlugin = require('mini-css-extract-plugin') const prodConfig={ mode: 'production', devtool: 'cheap-module-source-map', module: { rules: [ { test: /\.css$/, use: [ miniCssExtractPlugin.loader, 'css-loader', 'postcss-loader' ] }, { test: /\.scss$/, use: [ miniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 2, modules: true } }, 'postcss-loader', 'sass-loader' ] }, ] }, plugins: [ new miniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].chunk.css' }) ] } module.exports=merge(baseConfig,prodConfig)
const webpack=require('webpack') const merge = require('webpack-merge') const baseConfig=require('./webpack.base') const devConfig={ mode: 'development', // devtool: 'cheap-module-eval-source-map', module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader', 'postcss-loader' ] }, { test: /\.scss$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, modules: true } }, 'sass-loader', 'postcss-loader' ] }, ] }, plugins: [ new webpack.HotModuleReplacementPlugin() ], devServer: { contentBase: './dist', // open: true, //自动打开浏览器 // port: 8080, hot: true, //启用webpack的热模块替换功能 //hotOnly: true //devServer.hot在没有页面刷新的状况下启用热模块替换做为构建失败时的后备 } } module.exports=merge(baseConfig,devConfig)
"sideEffects": [ "*.css" ],
不然经过import './*.css'
方式引入的css文件会被删除掉。
运行npm run build
,咱们就能够看到dist目录下,css文件被单独分割出来了。
官网还提供一个插件能够对css文件进行压缩
安装
npm i optimize-css-assets-webpack-plugin -D
//引入 const optimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') //添加 optimization: { minimizer: [new optimizeCSSAssetsPlugin({})], },
再次运行npm run build
,打开打包后的css文件,就发现css文件被压缩了。
代码都已经上传到github:github:webpack4