安装webpack
npm init 帮助我们以node规范的方式创建一个项目或者创建一个node的包文件,最终的结果就是在项目目录下生成一个package.json文件。
npm init / npm init -y (自动生成默项)
npm install webpack webpack-cli --save-dev
运行webpack
npx webpack index.js // 用webpack翻译index.js文件
注意:
- npx 和 npm 的区别,其实很简单,npx会在目录下的node_modules下面找webpack,没有就安装一个。npm 则先去全局,然后再去当前目录下的node_modules找webpack,没有就不找了;
- devDependencies 和 dependencies的区别,devDependencies 指的是你本地开发环境下的依赖(比如webpack,babel以及一系列的loader等);dependencies 指的是你生产环境下打包所需要的依赖(比如你项目中用到了lodash,那么你就应该配置到这个下边)
webpack环境搭建
webpack是基于nodejs开发的模块打包工具。提升webpack打包速度,一定要安装最新稳定版本。 (1) node版本要使用最新稳定版;(2)webpack要使用最新版本;
webpack的安装方式
全局安装: npm install webpack webpack-cli -g
; (安装时,如果npm安装很慢,建议使用手机分享热点,就不会有该问题了) webpack-cli@3.1.2 webpack@4.26.0 全局安装特定版本:npm install webpack@4.26.0 webpack-cli -g
;
不推荐使用webpack全局安装;卸载全局安装的webpack npm uninstall webpack webpack-cli -g
项目中安装: npm install webpack webpack-cli --save-dev
或者npm install webpack webpack-cli -D
其中–save-dev等价于-D;
安装特定版本: npm install webpack@4.26.0 webpack-cli@3.1.2 --save-dev
此时无法运行webpack命令,如果要运行webpakc命令,需要执行npx webpack -v
, 输出webpack版本 npm info webpack
or npm view webpack versions
当不知道某个版本号是否存在时,查看webpack的所有版本号
webpack的配置文件
创建默认配置文件 --- webpack.config.js,默认文件名必须是wepack.config.js;若要修改默认文件名,可用以下命令:
npx webpack --config webpackconfig.js // 修改默认配置文件名为 webpackconfig.js
// webpack.config.js
const path = require('path');
module.exports = {
mode:'production', // 开发环境: development,bundle.js中代码不会被压缩
entry : './src/index.js',
/* entry : {
main: './src/index.js'
},*/
output : {
filename:'bundle.js',
path:path.resolve(__dirname,'dist') // 绝对路径:打包后的文件放在什么目录下
}
}
配置打包命令时,可用npm script 替代 npx webpack,需在package.json文件中进行配置,如下,运行npm run bundle即可
scripts:{
"bundle" : "webpack"
}
webpack-cli的作用就是使得我们可以使用 npx webpack 或 webpack 的命令。webpack默认的打包的模式是production。
什么是Loader
webpack 是什么? 模块是什么?(js,html, css,图片,其他文件) webpack的配置文件的作用是什么? loader 本质是一个打包的方案,它知道对于某一个特定的文件,webpack应该怎样打包,因为webpack无法识别非.js结尾的模块,loader在module中配置。安装file-loader : npm install file-loader --save-dev
。file-loader能够处理任何静态资源的文件,不止图片,例如excel,word , ppt,txt等。 file-loader底层的原理是:第一步,将文件移动到dist目录下;第二步:把该文件的地址返回给变量。
module:{
// rules 是一个对象数组
rules:[{
test:/\.(jpg|png|gif)$/,
use:{
loader:'file-loader',
options:{
// placeholder 占位符语法
name:'[name]_[hash].[ext]', // 图片文件以原来的名字输出
outputPath:'https://jerrychane.oss-cn-beijing.aliyuncs.com/images/'
}
}
}]
},
url-loader 除了能做 file-loader 做的事情,还能做额外的事情,url-loader会将图片转换成一个base64的字符串,安装 url-loader :npm install url-loader --save-dev
; 当图片非常小时,
module:{
rules:[{
test:/\.(jpg|png|gif)$/,
use:{
loader:'url-loader',
options:{
// placeholder 占位符语法,需要加引号,为字符串
name:'[name]_[hash].[ext]', // 图片文件以原来的名字输出
outputPath:'https://jerrychane.oss-cn-beijing.aliyuncs.com/images/',
limit:2048 // 大于limit,生成图片文件; 小于limit,生成base64字符串
}
}
}]
},
Vue Loader 是什么
Vue Loader 是一个 webpack 的 loader,它允许你以一种单文件组件的格式撰写 Vue 组件:
<template>
<div class="example">
{ { msg }}
</div>
</template>
<script>
export default {
data() {
return {
msg:'Hello Vue!'
}
}
};
</script>
<style>
.example {
color:red;
}
</style>
使用 loader 打包静态资源(样式篇一)
为了使webpack能够识别css文件,需要引入 style-loader , css-loader; 其中css-loader可以帮助我们识别几个.css文件之间的关系,最终帮我们合并成一段css;style-loader 是在得到 css-loader生成的内容之后,style-loader会将这段内容挂载在head部分,生成<style>...</style>
; 若要识别 .scss文件,还要引入 sass-loader。 安装loader:
npm install style-loader css-loader --save-dev
在webpack.config.js中配置:
{
test: /\.css$/,
use: ['style-loader','css-loader','sass-loader','postcss-loader']
}
安装sass-loader:
npm install sass-loader node-sass --save-dev
问题描述:执行上述命令后,执行 npm run bundle
, 项目报错:TypeError: this.getResolve is not a function
错误原因:安装 sass-loader 的版本为最新版本,由于版本过高,而导致编译出错; 解决方法:进入npm官方网站,搜索sass-loader,查看发布的版本号,选择一年前或两年前的低版本。我选择的是7.3.0版本,继续执行安装命令:
npm install sass-loader@7.3.0 node-sass --save-dev
在webpack中loader是有先后顺序的,按照从下到上,从右到左的执行顺序。例如以上文件中,首先通过sass-loader对sass文件进行翻译,翻译成css代码以后,给到css-loader,然后都处理好,通过style-loader挂载到页面上。 在写css3新特性时,一般要加浏览器厂商前缀,可以通过 postcss-loader ,自动添加各个浏览器厂商的前缀。首先安装 postcss-loader ,然后创建 postcss.config.js 文件,在该文件中写入配置规则。
npm i -D postcss-loader // 安装 post-loader
npm install autoprefixer -D // 安装 autoprefixer plugin
在postcss.config.js中配置:
module.exports = {
plugins: [
require('autoprefixer')
]
}
遇到的坑:
//scss中写的代码
body {
.avatar {
width: 150px;
height: 150px;
transform: translate(100px,100px);
}
}
结果在编译至css代码时,并未加上-webkit-前缀,需要在package.json中加入browserslist,其他地方无需任何修改,代码如下:
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
使用 loader 打包静态资源(样式篇二)
css-loader中常用的配置项:
use: [
'style-loader',
{
loader:'css-loader',
options:{
importLoaders:2, // 以import引入的.scss文件,也要走postcss-loader和scss-loader的2个loader
modules:true, // css文件的模块化打包,区分不同css的作用域
}
},
'sass-loader',
'postcss-loader'
]
在使用时,引入时 import style from ‘./index.scss’;
import avatar from "./avatar.jpg";
import style from './index.scss';
import createAvatar from './createAvatar';
// 调用createAvatar()函数
createAvtar();
var img = new Image();
img.src = avatar;
img.classList.add(style.avatar);// 只控制当前页面中img的样式,不影响createAvatar()中的样式
var root = document.getElementById('root');
root.append(img);
使用 file-loader打包字体文件:
{
test: /\.(eot|ttf|svg|woff|woff2)$/,
use: {
loader: 'file-loader',
}
}
使用plugins让打包更便捷
安装 html-webpack-plugin:
npm install html-webpack-plugin --save-dev // html-webpack-plugin@3.2.0
在 webpack.config.js 中的配置 plugins 数组:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({ // 实例化HtmlWebpackPlugin,在打包之后运行
template:'src/index.html' // dist目录中index.html的模板文件
})]
};
HtmlwebpackPlugin的作用,会在打包结束后,自动生成一个html文件,并把打包生成的js自动注入到这个html文件中。 webpack 中的 plugin 可以在webpack运行到某个时刻的时候(例如,htmlwebpackPlugin在打包结束的时刻),帮你做一些事情,类似于 vue 和 react 中的生命周期函数。 安装 clean-webpack-plugin,第三方的plugin:
npm install clean-webpack-plugin --save-dev // clean-webpack-plugin@1.0.0
在webpack.config.js中的配置:
var CleanWepackPlugin = require('clean-webpack-plugin');
var path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new CleanWepackPlugin(['dist'])]// 实例化CleanWebpackPlugin,在打包前运行
};
clean-webpack-plugin的作用是,在打包前删除上一次打包生成的文件。对比html-webpack-plugin和clean-webpack-plugin: 前者是在打包后执行(可通过控制台查看顺序);后者是在打包前执行。
Entry 与 Output 的基本配置
entry的入口文件可以写成一个字符串,表示打包文件的路径;output默认生成的文件为main.js。如果要将同一个文件,打包生成两个或多个文件,需要使用占位符( [name] / [hash] )。与此同时,会将打包生成的文件,放在template:’src/index.html’文件中。
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
sub:'./src/index.js'
},
output: {
filename: '[name].js', // name的值为entry中对象的key值,即main.js 和 sub.js
path: path.resolve(__dirname, 'dist')
}
}
在打包生成的文件前加入统一的地址,如引入CDN地址,需要在output中加上publicPath
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
sub:'./src/index.js'
},
output: {
publicPath:'http://cdn.jerrychane.com',
filename: '[name].js', // name的值为entry中对象的key值,即main.js 和 sub.js
path: path.resolve(__dirname, 'dist')
}
}
SouceMap的配置
在开发者模式(mode:’development’),默认sourceMap已经配置,如果要关掉soucemap,可设置 devtool:’none’; souceMap本质上是一个映射关系,可以映射出打包后文件的源代码所在源文件所在的位置。 开启这种映射关系,可设置devtool:’source-map’,在打包时,会在dist目录下生成*.js.map
的文件;若要去掉dist目录下的*.js.map
文件可设置 devtool:’inline-source-map’, 将*.js.map里内容直接放在打包文件的底部,变成base64位的一段代码;
devtools:'inline-source-map'; // 可以精确到哪一行的哪一列的记录,这样的映射很耗费性能
devtools:'cheap-inline-source-map'; // 可以精确到哪一行的记录,不用告诉哪一列,这样可以提高性能,只映射业务代码,不会管第三方的loader
devtools:'cheap-module-inline-source-map';// 映射业务代码和第三方的loader
devtool:'eval';// 通过eval执行js代码,执行效率最快,性能最好,但复杂代码慎用
最佳实践:在开发环境(mode: ‘devlopment’)中使用sourceMap,最好使用devtool:'cheap-module-eval-source-map'
;在生产环境(线上环境mode: ‘production’)建议使用devtool:'cheap-module-source-map'
。 cheap 可以提升打包速度,eval是打包速度最快,性能最好的方式, 原因是 eval 是通过eval的执行形式来生成sourceMap的映射关系。
使用WebpackDevServer提升开发效率
实现代码即时更新有三种方法: 第一种,在package.json中添加watch监听配置:
"scripts": {
"watch": "webpack --watch", // 运行npm run watch,监听源文件代码的修改,实现自动打包,但无法实现自动打开浏览并刷新的特性
},
第二种,在webpack.config.js中添加devServer配置(推荐使用): 首先要在当前项目中安装webpack-dev-server,安装命令如下:
npm install webpack-dev-server -D // 安装webpack-dev-server@3.1.10
# webpack.config.js
devServer:{
contentBase:'./dist', // web服务器的地址即根路径下的dist目录,默认是localhost:8080
open:true, // 自动打开浏览器,自动访问服务器地址
port:8081,
proxy: {
'/api': 'http://localhost:3000' // 配置代理地址
}
},
# package.json
"scripts": {
"start": "webpack-dev-server"
},
webpack-dev-server不仅可以监听到源文件代码的修改,实现自动打包,而且可以自动打开浏览器,并自动刷新浏览器,从而提升我们代码开发的效率。 通过file的形式打开html页面,就没办法去发ajax请求了,因为发ajax请求必须dist/index.html必须在一个服务器上,通过http协议的方式打开。 在devServer中可以添加proxy项,用来配置跨域代理地址,进行接口模拟,从而解决跨域问题;也可以配置端口号port。 扩充知识:可以使用express实现devServer创建本地http服务器的功能,需要安装expess和webpack-dev-middleware。
npm install express -D // express@4.16.4
npm install webpeck-dev-middleware -D // webpack-dev-middleware@3.4.0
# server.js
const expess = require('express');// 创建http服务器
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');// webpack中间件,监听打包
const config = require('./webpack.config.js');
const complier = webpack(config); // webpack编译器
const app = expess();
app.use(webpackDevMiddleware(complier,{
publicPath:config.output.publicPath
}))
app.listen(3000,()=>{
console.log('server is running');
})
运行webpack方式:第一种为在命令行中运行webpack index.js; 第二种是在node中运行,如node server.js。
Hot Module Replacement 热模块替换
webpack-dev-server在运行npm run start时,并不会生成dist目录并存放打包生成后的文件,而是将打包生成后的文件放在内存中,可以有效地提升打包的速度,让我们的开发更快。 HRM只替换页面中css的改变,而不改变页面中原有的js渲染的内容,方便我们调试css样式。修改webpack配置文件后,最好重启一下命令。
devServer:{
contentBase:'./dist',
open:true,
port:8080,
hot:true, // 开启HotModuleReplacement功能
hotOnly:true // 即使html未生效,浏览器也不自动刷新
},
const webpack = require('webpack');// webpack.config.js头部引入webpack
plugins:[
new webpack.HotModuleReplacementPlugin()
],
当在一段代码中引入其他的模块的时候,如果你希望这个模块发生了变化,只去更新这个模块的代码,就要用的HRM技术了。需在webpack.config.js的devServer中配置hot和hotOnly项,在plugins中引入 HotModuleReplacementPlugin插件。
// 要实现局部刷新需要使用到accept方法,接收两个参数,第一个是要变更的模块,第二个是变更的回调函数
if (module.hot) {
module.hot.accept("./number", () => {
document.body.removeChild( document.getElementById('number'))
number();
})
}
使用Babel处理ES6语法
当使用npx webpack命令做打包,可以查看打包生成的main.js文件;而使用npm run start,用webpack-dev-server做打包,打包生成的文件都在内存中,无法查看main.js。 如果main.js中能够将ES6打包生成的语法转换成为ES5的语法,就能够兼容所有的浏览器,此时就需要借助Babel来实现,可访问其官方网站https://babeljs.io , 在setup - webpack 目录下找到其在webpack的详细配置。 babel-loader只是babel与webpack之间的一座桥梁,用来连接webpack,并不对ES6语法进行翻译。@babel/core是babel的一个核心库,能够让babel去识别js代码里的内容,把js代码转换成ast抽象语法树,最后将语法树编译成新的js语法。
# 安装babel-loader和@babel/core
npm install --save-dev babel-loader @babel/core // babel-loader@8.0.4 @babel/core@7.2.0
# webpack.config.js
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader",
options:{
presets:["@babel/preset-env"]
}
}
]
}
preset-env是真正将ES6转换成ES5语法的模块,里面包含了所有ES6翻译到ES5的语法翻译规则。
npm install --save-dev @babel/preset-env // @babel/preset-env@7.2.0
babel-polyfill配置
只有babel-loader和preset-env没办法解决变量或函数(例如:promise、map方法)在低版本浏览器中的识别,需要借助babel-ployfill,补充实现变量或函数在低版本的浏览器中被识别。
npm install --save @babel/polyfill@7.0.0 // @babel/polyfill@7.0.0
#/src/index.js
import "@babel/polyfill"; // 使用@babel/polyfill
# webpack.config.js 配置
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader',
options:{
presets:[
['@babel/preset-env', {
targets: {
edge: "17",
firefox: "60",
chrome: "67",// 在chrome版本低于67时进行babel的翻译
safari: "11.1",
},
useBuiltIns:'usage' // 根据业务代码,按需加入ES6代码翻译
}
]
]
}},
}
chrome: “67”表示在chrome版本低于67时进行babel的翻译,如果浏览器版本支持从ES6语法,就会忽略转译成ES5。polyfill的相关参数,可以去官网查看相应的配置。 在编写业务代码时,如果需要用到babel,可以参考上面的配置方案。但是,这个方案并非所有场景都能使用。
babel-plugin-transform-runtime
在开发一个类库、第三模块、或者组件库,如果用babel-pollyfill方案,会出现问题。因为pollyfill在注入类似Promise,map方法时,会通过全局变量的方式来注入,会污染全局环境。 安装 plugin-transform-runtime(参考: https://babeljs.io/docs/en/babel-plugin-transform-runtime ) :
npm install --save-dev @babel/plugin-transform-runtime //@7.2.0
此外还要安装 runtime 模块:
npm install --save @babel/runtime //@7.2.0
# webpack.config.js配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options:{
"plugins": [["@babel/plugin-transform-runtime",{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
}]]
}
},
......
如果webpack.config.js中配置了”corejs”:2,则还需要安装一个依赖包 , 否则会报promise模块找不到的错误,然后运行 npx webpack 打包,就不会有任何问题了:
npm install --save @babel/runtime-corejs2 //@7.2.0
babel的配置项会非常多,webpack.config.js中options的内容会非常长,可以在项目根目录下创建.babelrc文件,将options里的内容添加到.babelrc中。
#.babelrc
"plugins": [
["@babel/plugin-transform-runtime",{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
}]
]
Webpack 实现对React框架代码的打包
配置 React 代码的打包
参考url: https://babeljs.io/docs/en/7.1.0/babel-preset-react 可以通过 @babel/preset-react 来解析 react 中的 jsx 语法,首先需要安装 react&react dom;
npm install react react-dom --save // react@16.6.3 react-dom@16.6.3
想要使用webpack结合babel打包react的代码,还需要安装@babel/preset-react:
npm install --save-dev @babel/preset-react // @babel/preset-react@7.0.0
同时.babelrc里也需要做相应的配置,.babelrc里文件的执行顺序是从下到上,从右到左。先把react里的代码进行转换,然后把转换后代码中的ES6代码部分进行转换为ES5。
{
presets:[
["@babel/preset-env",{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns:"usage"
}
],
"@babel/preset-react"
]
}
Tree Shaking
如果在webpack.config.js配置了babel-loader相关的内容,同时.babelrc里加入了”useBuiltIns”,那么在业务代码中就不需要引入polyfill。
// import "@babel/polyfill"; 无需引入
Tree Shaking 的意思是把一个模块中没用的东西都摇晃掉,一个模块可以理解为一颗树。只引入我们需要的部分,不需要的部分通过 Tree Shaking帮我们摇晃掉,剔除掉。在webpack中,要实现Tree Shaking 我们应该怎么做呢? 首先Tree Shaking只支持ES Module的引入,即 import方式的引入,使用cont add = require(…)等CommonJS的方式引入,Tree Shaking的方式是不支持的。这是因为import这种ES Module的引入,底层是一个静态引入方式,而CommonJS底层是动态引入的方式,所以Tree Shaking 只支持静态引入方式。
Tree Shakingの配置
mode:”development”下默认是没有Tree Shakin功能的,可以在webpack.config.js的plugins下面添加一个optimization的对象;同时在package.json中添加 “sideEffects”:fale,意思是tree shaking 对所有的模块进行tree shaking,没有特殊处理的部分。
# webpack.config.js
optimization:{
usedExports:true
},
# package.json
{
"name": "lesson2",
"sideEffects":false,
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server"
},
......
在mode:’development’环境下,tree shaking 并不会通过减少代码的方式,来处理多余模块,只是会做一个提示。 如果要排除某些文件不做tree shaking,可在package.json中添加sideEffects:[‘*.css’],没有需要排除的直接写 “sideEffects”:false。 在mode:’production’环境下,webpack已经拥有一些自动的tree shaking配置,可以注释掉webpack.config.js中的optimization配置。但是,package.json中的sideEffects还是需要写的。
Development 和 Production 模式的区分打包
在开发环境下使用development,一旦在开发环境开发完代码,需要部署上线时,就要用的production模式。development 和 production 的差异主要在几个方面:
- 在开发环境中,sourcemap是非常完整的;在生产环境中,sourcemap是非常简洁的;
- 在开发环境中,代码一般不需要压缩;在生产环境中,代码一般是被压缩过的代码;
为了区分development和production的配置文件,可以分别创建webpack.dev.js 和 webpack.prod.js文件,在package.json的scripts做如下配置:
# package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
},
build一般指打包上线后的代码,dev是开发时的代码。当运行 npm run dev时,如果不想手动刷新浏览器页面,自动刷新,可以将webpack.dev.js中的hotOnly:true去掉,改过webpack的配置文件后,一定要重启服务器。 如果要上线,则运行npm run build,会产生一个dist目录,将dist目录下的文件上传至服务器,就可以完成产品的上线。 当webpack.dev.js和webpack.prod.js中,有大量相同的配置代码,可以在根目录下创建webpack.common.js,然后将webpack.dev.js和webpack.prod.js中共用的代码,提取到webpack.common.js中。 最后要将webpack.common.js和webpack.dev.js的配置做合并,再输出,此时,需要引入第三方的模块,叫做webpack-merge。
npm install webpack-merge -D // webpack-merge@4.1.5
在webpack.dev.js中引入webpack-merge
const merge = require('webpack-merge');
此时这三个文件的配置如下:
# webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// presets:[
// ["@babel/preset-env",{
// targets: {
// edge: "17",
// firefox: "60",
// chrome: "67",
// safari: "11.1",
// },
// useBuiltIns:'usage' // 根据业务代码,按需加入ES6代码翻译
// }]
// ]
}
},
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'https://jerrychane.oss-cn-beijing.aliyuncs.com/images/',
limit: 10240
}
}
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}, {
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),
// new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
# webpack.prod.js
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode: 'production',
// devtool: 'cheap-module-eval-source-map',
devtool: 'cheap-module-source-map',
// devServer: {
// contentBase: './dist',
// open:true,
// port:8080,
// hot:true,
// hotOnly:true
// },
// optimization:{
// usedExports:true
// },
}
module.exports = merge(commonConfig, prodConfig);
# webpack.dev.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
// devtool: 'cheap-module-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true,
// hotOnly:true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
},
}
module.exports = merge(commonConfig, devConfig);
可以将webpack.dev.js,webpack.prod.js,webpack.common.js文件统一放在一个文件夹中,例如build文件夹,才是需要修改package.json。
# package.json
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js",
},
Webpakc和Code Spliting
Code Spliting 的意思是代码分割,代码与webpack之间的关系是什么呢? 为了说明两者的关系,需要在开发环境下,通过打包文件来查看,此时在package.json中可以创建一个新的脚本命令, dev-build:
# package.json
"scripts": {
"dev-build":"webpack --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
此时会在build目录下生成一个dist目录,需要在webpack.common.js中修改output配置中dist目录的位置,同时要修改clean-webpack-plugin中的目录。
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'],{
root: path.resolve(__dirname, '../') // 防止在dist目录下生成未参与打包的文件
}),
// new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../dist')
}
webpack的配置和插件巨多,要想完全掌握是不可能的,需要分析在打包的流程中,一步一步地分析具体哪一个流程出了问题,找到问题,截取出来到搜索引擎或论坛上寻找解决问题。 接下来引入本地的正题,Code Spliting与Webpack之间具体是什么关系呢? 首先安装一个第三方的包lodash,它是一个功能集合,提供了很多功能和方法,可以让我们高性能地去使用一些,比如说字符串拼接的函数等等。
npm install lodash --save // lodash@4.17.11
在src/index.js中写入一些代码,然后 npm run dev-build打包,在浏览器中运行dist目录下的index.html:
import _ from 'lodash'; // 同步打包
console.log(_.join(['a','b','c'],'***'));
// 此处省略10万行业务逻辑;
console.log(_.join(['a','b','c'],'***'));
当业务逻辑代码较多时,带来的问题是:打包文件会很大,加载时间会很长;当修改业务代码时,需要重新打包,用户需要重新加载main.js,才能获取业务代码。 第一种方式,首次访问页面时,加载main.js(2mb),当业务逻辑发生变化时,又要加载2mb的内容;第二种方式,main.js被拆成了 lodash.js(1mb),main.js(1mb),当业务逻辑发生变化时,只需要加载main.js(1mb)即可。 在一些项目之中,通过对代码公用部分进行拆分,来提升项目的运行速度,提升性能和用户体验。Code Spliting 本质上与 Webpack没有任何关系,只是webpack中有很多插件能够很方便的实现Code Spliting也就是代码分割的功能。在Webpack4中,split-chunks-plugin与webpack做了捆绑,不用安装,直接可以用。 在webpack.common.js中可以配置如下:
optimization:{
splitChunks:{
chunks:'all'
}
},
还可以通过另外一种异步引入方式,进行webpack中的代码分割,不用配置webpack.common.js。
npm install babel-plugin-dynamic-import-webpack -D //@1.1.0
# src/index.js
function getComponent() {
return import('lodash').then(({default:_}) => { // 异步打包
var element = document.createElement('div');
element.innerHTML = _.join(['Jerry','Chane'],'_');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
# .babelrc
{
presets:[
["@babel/preset-env",{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns:"usage"
}
],
"@babel/preset-react"
],
plugins:["babel-plugin-dynamic-import-webpack"]
}
运行npm run dev-build,会发现lodash.js代码会被打包到dist/0.js目录下。 总结:代码分割,和webpack无关;webpack中实现代码分割,有两种方式,同步方式只需要在webpack.common.js中做optimization的配置,异步代码,无需做任何配置,会自动进行代码分割,放置到新的文件中。
SplitChunksPlugin 配置参数详解
在CodeSpliting中,打包产出的文件0.js需要修改为一个可以识别的名字,改怎么办呢?在异步加载的代码中,有一种注释代码叫做Magic Comment,首先要移除package.json中的插件”babel-plugin-dynamic-import-webpack”: “^1.1.0”, .babelrc中也去掉 plugins:[“babel-plugin-dynamic-import-webpack”] ,改为 “plugins”: [“@babel/plugin-syntax-dynamic-import”] ,而index.js文件中代码如下:
npm install --save-dev @babel/plugin-syntax-dynamic-import // @7.2.0
# src/index.js
function getComponent() {
return import(/*webpackChunkName:"lodash"*/'lodash').then(({default:_}) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Dell','Lee'],'_');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
其中注释webpackChunkName:”lodash”,就是npm run dev-build后生成的vendor-lodash.js文件。如果要使生成的文件名字为lodash.js,前面不带vendor,则需要对webpack.common.js 进行一下配置。
# webpack.common.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors:false,
default:false
}
}
},
无论是同步代码的分割还是异步代码的分割,都需要splitChunks,最开始可以将splitChunks设置为一个空对象,照样可以进行代码的分割。因为splitChunks默认的配置项里,已经将所有的配置都补全完整了。
# webpack.common.js
splitChunks: {
chunks: 'async', // 只对异步代码生效
minSize: 30000, // 引入模块大于30000个字节即30kb,就做代码分割
maxSize: 0,
minChunks: 1, // 模块被引入1次就分割打包
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: '~',
name:true,
cacheGroups: { // 缓存组
vendors: false,
default: false
}
}
这里会有一个坑,官方文档中有minRemainingSize: 0,这一项,在打包的时候会无法识别,需要将这一项注释掉。 当设置chunks:’all’ 后还无法实现代码分割,此时需要对cacheGroups进行如下配置:
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,// node_modules引入模块进行分割
priority: -10,
filename:'vendors.js'
},
default: false
}
当对非node_modules中的模块进行打包时,就要用到cacheGroups中的default配置:
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
filename:'vendors.js'
},
default: {
priority: -20,
reuseExistingChunk: true,
filename:'common.js'
}
}
在打包同步代码时,除了走cacheGroups即缓存分组上面的配置,也会走cacheGroups里的配置,并根据priority的大小,有限走priority大的分组配置。
Lazy Loading 懒加载,Chunk是什么
懒加载是通过import异步加载一个模块,这个概念并不是webpack中的,而是ES中的Promise类型。在代码中如果要使用这种import,必须要引入babel-polyfill或promise-polyfill等等,因为在低版本浏览器下,很有可能不支持Promise的语法。
function getComponent() {
return import(/*webpackChunkName:"lodash"*/ 'loadsh').then(({default:_})=>{
// 逻辑||业务代码
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '_');
return element;
})
}
document.addEventListener('clikc',()=>{
getComponent(element).then(()=>{
document.body.appendChild(element);
})
})
在ES7中引入了异步函数的概念,上面的代码可以改造如下:
async function getComponent() {
const { default: _ } = await import( /*webpackChunkName:"lodash"*/ 'lodash');
const element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '_');
return element;
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element);
});
})
Chunk是什么?在webpack打包后,dist目录下生成的一个js文件,就叫做一个chunk。在webpack.common.js中splitChunks,一般配置一个chunks:’all’即可,其他均为默认配置。
optimization: {// loadsh.js
splitChunks: {
chunks: 'all', //对同步和异步代码都进行代码分割,默认async
// minSize: 30000,
// minRemainingSize: 0,
// maxSize: 50000,// 50kb,lodash 1mb
// minChunks: 2,
// maxAsyncRequests: 6,
// maxInitialRequests: 4,
// automaticNameDelimiter: '~',
// name:true,
// cacheGroups: {
// vendors: false,
// default: false
}
}
},
打包分析:Preloading&Prefetching
打包分析指的是当我们使用webpack进行代码的打包之后,可以借助打包分析的一些工具,对打包生成的文件一定的分析,然后来看打包是否合理。 首先需要访问webpack打包分析的一个github仓库,执行命令npm run dev-build生成stats.json文件。
# https://github.com/webpack/analyse
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
stats.json中是对整个打包文件的一个描述性的文件,将该文件上传至http://webpack.github.com/analyse,可以看可视化的分析。开发人员使用较多的是插件webpack-bundle-analyzer。 当页面需要在第一次加载时,提升js代码的使用率。查看代码使用率的方法,在控制台输入ctrl + shilf + p,输入coverage, 点击Show Coverage。webpack官方推荐,多写一些异步的代码,才能使代码的性能真正的得到提升,这也是webpack为什么默认的配置项里splitChunks的chunks:’async’。 Preloading&Prefetching 在网络带宽空闲的时候,预先将click.js文件加载好。webpackPrefetch会等核心的代码加载完成后,页面带框空闲的时候再去加载click.js等需要懒加载的文件;webpackPreload是和核心的代码一起加载。
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */'./click.js').then(({ default: func }) => {
fun(); // func指的是handleClick()函数
});
});
document.addEventListener('click', () => {
import(/* webpackPreload: true */'./click.js').then(({ default: func }) => {
fun(); // func指的是handleClick()函数
});
});
在做前端代码性能优化时,缓存并不是最重要的点,最重要的点应该是code coverage即代码有效使用率上。
CSS 文件的代码分割
chunkFileName
通常会见到在webpack中output项配置了chunkFileName:’[name].chunk.js’,例如在src/index.js中:
async function getComponent() {
const { default: _ } = await import( /*webpackChunkName:"lodash"*/ 'lodash');
const element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element);
})
})
运行npm run dev-build,可以在dist目录下生成loadash.chunk.js文件。入口文件即entry:{main: ‘./src/index.js’,} 会走filename: ‘[name].js’的配置参数,而间接引入的模块文件,会走chunkFilename:’[name].chunk.js’的配置参数。
MiniCssExtractPlugin
在没有安装MiniCssExtractPlugin插件时,webpack默认会将.css文件打包到js文件中。如果index.js中引入的文件为.css文件,将.css文件单独打包,在dist目录下生成style.css文件,则需要用到MiniCssExtractPlugin插件。
npm install --save-dev mini-css-extract-plugin@0.5.0
由于0.5.0版本只支持线上环境的打包(npm run build),所以在webpack.prod.js中需要引入该插件;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
同时在webpack.pro.js中进行配置:
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css',
})],
module: {
rules: [{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}],
},
在package.json中配置:
"sideEffects": [
"*.css"
],
optimize-css-assets-webpack-plugin
该插件可以自动将抽离出来的css代码文件进行合并和压缩。
npm install --save-dev optimize-css-assets-webpack-plugin@5.0.1
# webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
Webpack 与浏览器缓存( Caching )
去掉webpack中warning警告,在配置项中添加performance:false。vendor.js中一般会放置第三方的公共模块的代码,即node_modules下的模块,如果需要对vendor.js进行修改,其配置项如下。
// webpack.common.js
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test:/[\\/]node_modules[\\/]/,
priority:-10,
name:'vendors',
},
}
}
为了能够使浏览器在开发环境不使用缓存,而在生产环境中使用缓存,需要用到contenthash占位符,在webpack.dev.js和webpack.prod.js中对output项进行如下的配置:
// webpack.dev.js
output:{
filename: '[name].js',
chunkFilename:'[name].js',
}
// webpack.prod.js
output:{
filename: '[name].[contenthash].js', // 只要不修改文件,hash值永远不变
chunkFilename:'[name].[contenthash].js',
}
如果源代码没有做变更,用户可以访问本地缓存的文件;如果源代码发生变更,文件对应的hash值将发生变化,用户必须到服务器上加载新的文件。通过contenthash,用户只需要更新有变化的代码,没有变化的代码,用户可以使用本地的缓存。 main.js中放置的业务逻辑的代码,vendor.Js放置的是第三方的库,vendor.js和main.js中间关联的代码放在manifest.js中,默认manifest存在于main.js中,也存在于vendor.js中。在老版本中,即使没有修改代码,contenthash也会发生改变,这时需要在webpack.common.js中配置runtime, 会生成runtime.js,将main.js和vendor.js之间的关系,存放在runtime.js中。
// webpack.common.js
optimization: {// loadsh.js
runtimeChunk:{
name:'runtime'
}
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test:/[\\/]node_modules[\\/]/,
priority:-10,
name:'vendors',
},
}
}
Shimming(垫片)的作用
能够解决webpack打包过程中的一些兼容性问题,不局限于浏览器的高低版本。
// webpack.common.js
plugins: [
new webpack.ProvidePlugin({
$:'jquery',
_:'lodash',
_join:['lodash','join']
}),
]
在index.js中打印this,发现this永远指向这个模块自身,而非window全局变量。如果想要每一个js中的this都指向window全局变量,需要安装一个imports-loader,实现this指向window全局变量。
npm install imports-loader@0.8.0 --save-dev
// webpack.common.js
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use:[{
loader:'babel-loader'
},{
loader:'imports-loader?this=>window'
}],
},
环境变量的使用方法
在package.json中传递env环境变量,给它一个属性为production,默认值为true。
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.common.js",
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js"
},
在webpack.common.js中引入devConfig和prodConfig模块以及merge,当在开发环境时走devConfig的配置文件,在生产环境则走prodConfig的配置。
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge');
const devConfig = require('./webpack.dev.js');
const prodConfig = require('./webpack.prod.js');
const commonConfig = {
....
}
module.exports = (env) => {
if (env && env.production) {
return merge(commonConfig, prodConfig);
} else {
return merge(commonConfig, devConfig);
}
}
Webpack实战配置案例
Library的打包
npm install webpack@4.27.1 webpack-cli@3.1.2 --save
创建文件夹library,创建文件math.js,string.js.index.js,webpack.config.js文件,代码如下:
// math.js
export function add(a, b) {
return a + b;
}
export function minus(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function division(a, b) {
return a / b;
}
// string.js
export function join(a,b) {
return a + '' + b;
}
// index.js
import * as math from './math';
import * as string from './string';
export default { math, string };
//webpack.config.js
const path = require('path');
module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'library.js'
}
}
如果要使我们的库文件,以多种形式都能被引用。则需要在output多加一个参数libraryTarget:’umd’,表示以任何形式引入,该库都能引用被引用到,支持的语法格式如下。
import library from 'library';
const library = require('library');
require(['library'],function(){});
如果需要支持<script sr='library.js'></script>
,并通过library.math方法,则需要在output配置参数library:’library’。
module.exports = {
mode:'production',
entry:'./src/index.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:'library.js',
library:'library',
libraryTarget:'umd' // value => this,window,global
}
}
如果将上述libraryTarget中的‘umd’修改成‘this’,则表示将library挂载到全局的this中即this指向library,library将不再支持import和require的方式引入了。
npm install lodash@1.0.0 --save
在webpakc.config.js中加入参数externals:[“lodash”],表示遇到lodash时,忽略该库,不要把它打包进代码中;
module.exports = {
mode:'production',
entry:'./src/index.js',
externals:["lodash"],// {lodash:{commonjs:'lodash'}}
output:{
path:path.resolve(__dirname,'dist'),
filename:'library.js',
library:'library',
libraryTarget:'this'
}
}
如何将库文件发布到npm官网上呢?首先修改package.json中main的配置参数为”main”: “./dist/library.js”,在命令行中运行npm adduser,填写完账号密码后,运行npm publish,就能将我们打包好的库文件发送到npm的官网。别人如果要使用这个库文件,直接npm install就可以了。库的名字(在npm中是独一无二的)就是package.json中name的值。
PWA的打包
PWA的全称是Progressive Web Application。模拟后端服务器,需要安装一个http-server服务器。
npm install http-server@0.11.1 --save-dev
在package.json中修改script,目的是能够让dist目录下的文件,通过npm start在http-server下启动起来。
"scripts": {
"start":"http-server dist",
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js"
},
在浏览器中访问 http://localhost:8080/index.html 即可访问dist目录下的文件。pwa可以实现一个什么样的效果呢?第一次访问某个网站,访问成功了,突然之间服务器挂掉了;第二次再重新这个网站时,可以利用本地的缓存,直接将之前访问的网站再展示出来。这样即使服务器挂掉了,在本地还是能够看到之前访问的页面。 要想实现pwa的功能,可以利用第三方(Google封装)的模块workbox-webpack-plugin。
npm install workbox-webpack-plugin@3.6.3 --save-dev
只有线上的代码才需要考虑pwa的技术,本地开发的代码无需pwa,需要在webpack.prod.js进行配置。
const WorkboxPlugin = require('workbox-webpack-plugin');
...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css',
}),
new WorkboxPlugin.GenerateSW({ // 注意GenerateSW的大小写
clientsClaim: true,
skipWaiting: true
}),
],
npm run build后会在dist目录下生成precache-manifest.js和service -worker.js文件,通过这两个文件就能使service worker生效,使我们的项目支持pwa的功能。service worker可以理解为一种另类的缓存。
// src/index.js
console.log('hello,this is jerrychane');
if('serviceWorker' in navigator) {
window.addEventListener('load',()=>{
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('service-worker registed');
}).catch(error =>{
console.log('service-worker register error');
})
})
}
TypeScript 打包配置
TypeScript可以规范我们的代码风格,也可以方便的提示错误。首先初始化项目,然后安装webpack。
npm install webpack@4.28.3 webpack-cli@3.2.0 --save-dev
TypeScript官方推荐的loader是ts-loader,需要进行依赖环境包安装,同时在根目录下创建ts的配置参数。
npm install ts-loader@5.3.2 typescript@3.2.2 --save-dev
touch tsconfig.json
tsconfig.json的配置项,可以参考TypeScript官网提供的配置,这里只写几个常用的配置项。
{
"compilerOptions":
{
"outDir": "./dist",
"module": "es6",
"target": "es5",
"allowJs": true // 允许tsx中引入js的模块
}
}
TypeScript中引入lodash等第三方的库文件以及对应的types文件,对错误参数进行校验,以避免我们调用的错误。
npm install lodash@4.17.11 --save-dev
npm install @types/lodash@4.14.119 --save-dev
npm install @types/jquery --save-dev
npm install @types/vue --save-dev
使用WebpackDevServer实现请求转发
使用2-11课程目录的文件,运行npm run start,如果页面没有显示hello world,则需要到浏览器console控制台,找到application,取消掉在浏览器中注册的service worker。
npm install axios@0.18.0 --save-dev
在进行axions请求路径配置时,一般会使用相对路径,而不会使用绝路径。
// webpack.config.js
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true,
hotOnly: true,
proxy:{
'/react/api':'http://www.dell-lee.com',// 当请求'/react/api'开头的接口时,都会转发到'http://www.dell-lee.com'的服务器
}
},
当请求’/react/api’开头的接口,如果暂时无法拿到header.json,可以通过配置pathRewrite间接地拿到demo.json的数据。其配置如下:
proxy: {
'/react/api': {
target: 'http://www.dell-lee.com',
pathRewrite: {
'header.json': 'demo.json'
}
},
}
proxy只是方便我们在开发环境中做线上接口的转发,在线上环境即生产环境中不会进行转发,因为没有devServer,线上接口的地址,应该在初次写代码时就要写好。 此外,如果请求的地址是https的接口,还要在target下面配置一个secure:false,这样才能对https的接口进行请求转发。
WebpackDevServer解决单页面应用路由问题
npm install react-router-dom@4.3.1 --save-dev // 安装路由的依赖包
需要在webpack.config.js的devServer中配置:historyApiFallback: true, 保证路由不进行跳转,src/index.js中路由如下:
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import Home from './home.js';
import List from './list.js';
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route path='/' exact component={Home}/>
<Route path='/list' component={List}/>
</div>
</BrowserRouter>
)
}
}
ReactDom.render(<App />, document.getElementById('root'));
Eslint在Webpack中的配置(一)
Eslint是用来约束代码编写规范的工具,具体怎么约束,可以进行自定义配置。(该方法较复杂)
npm install eslint@5.12.0 --save-dev
npx eslint --init // 初始化生成.eslintrc.js文件
npm install babel-eslint@10.0.1 --save-dev
npx eslint src
Eslint在Webpack中的配置(二)
借助eslint-loader来完成
npm install eslint-loader@2.1.1 --save-dev
在webpack.config.js中的devServer配置overlay:true和eslint-loader:
devServer: {
overlay:true, // 将错误信息显示在浏览器中并弹出
...
}
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use:['babel-loader','eslint-loader']
},
...
}
提升webpack打包速度的方法
1.跟上技术的迭代(Node,Npm,Yarn); 如果想提升webpack打包的速度,可以升级webpack、node、npm、yarn等相关工具的版本。 2.在尽可能少的模块上应用Loader; 3.Plugin尽可能精简并确保可靠(webpack官方提供的插件); 4.resolve参数合理配置;
module.exports = {
entry: {
main: './src/index.js',
},
resolve:{
extensions:['.js','.jsx'],//查询目录下的以.js和.jsx结尾的文件,节约查找的时间
mainFiles:['index','child'],//查询目录下的index.jsx或child.jsx,一般不需要配置
alias:{
dellee:path.resolve(__dirname,'../src/child/')
}
},
...
}
5.使用DLLPlugin提高打包速度:对于第三方模块,其代码一般是不会变的,可以将其单独生成一个文件,只在第一次打包的时候,进行打包;第二次进行打包的时候,不再对第三方的模块进行分析打包。
// webpack.dll.js
const path = require('path');
module.exports = {
mode:'production',
entry:{
vendors:['react','react-dom','lodash']
},
output:{
filename:'[name].dll.js',
path: path.resolve(__dirname, '../dll')
}
}
npm install add-asset-html-webpack-plugin@3.1.2 --save-dev
安装add-asset-html-webpack-plugin 插件,并对 webpack.common.js 进行配置,AddAssetHtmlWebpackPlugin将第三方库挂载在全局变量上。
// webpack.common.js
plugins: [
....
new AddAssetHtmlWebpackPlugin({
filepath:path.resolve(__dirname, '../dll/vendors.dll.js')
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll/vendors.manifest.json'),
})
],
我们引入第三方模块的时候,要去使用dll中的vendors.dll.js文件,需要对vendor.dll.js文件进行映射。
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
...
plugins:[
new webpack.DllPlugin({
name:'[name]',
path:path.resolve(__dirname, '../dll/[name].manifest.json'),
})
]
}
// webpack.common.js
const path = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
const plugins = [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
];
const files = fs.readdirSync(path.resolve(__dirname,'../dll'));
console.log(files);
files.forEach(file => {
if (/.*\.dll\.js/.test(file)) {
plugins.push(
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if (/.*\.manifest\.json/.test(file)) {
plugins.push(
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
module.exports = {
entry: {
main: './src/index.js',
},
resolve: {
extensions: ['.js', '.jsx'],
},
plugins,
...
}
6.控制包文件大小 7.thread-loader,parallel-webpack,happypack多进程打包 8.合理使用 sourceMap 9.结合 stats 分析打包结果 10.开发环境内存编译 11.开发环境无用插件剔除
多页面打包配置
// webpack.common.js
const path = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
const makePlugins = (configs) => {
const plugins = [
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
}),
];
Object.keys(configs.entry).forEach((item) => {
plugins.push(new HtmlWebpackPlugin({
template: 'src/index.html',
filename: `${item}.html`,
chunks: ['runtime', 'vendors', item]
}))
});
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
if (/.*\.dll\.js/.test(file)) {
plugins.push(
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if (/.*\.manifest\.json/.test(file)) {
plugins.push(
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
});
return plugins;
}
const configs = {
entry: {
index: './src/index.js',
list:'./src/list.js',
detail:'./src/detail.js'
},
resolve: {
extensions: ['.js', '.jsx'],
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader'
}]
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'https://jerrychane.oss-cn-beijing.aliyuncs.com/images/',
limit: 10240
}
}
}, {
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}]
},
optimization: {
runtimeChunk: {
name: 'runtime'
},
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors',
}
}
}
},
performance: false,
output: {
path: path.resolve(__dirname, '../dist')
}
}
configs.plugins = makePlugins(configs);
module.exports = configs;
如何编写一个loader
可以参考 webpack 官网中 Documentation 中的 API
npm install webpack@4.29.0 webpack-cli@3.2.1 --save-dev
loader本质上就是一个函数(非箭头函数,会改变this的指向)
// makeloader/loaders/replaceLoader.js
module.exports = function(source) {
return source.replace('dell', 'dell-lee');
}
webpakc.config.js怎么用上面的loader呢?
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js/,
use: [path.resolve(__dirname, './loaders/replaceLoader.js')]
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
此外也可以对loader进行参数配置:
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules: [
{
loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
options:{
name:'lee'
}
}
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
// makeloader/loaders/replaceLoader.js
module.exports = function(source) {
console.log(this.query);
return source.replace('dell', this.query.name);
}
npm intall loader-utils@1.2.3 --save-dev
当使用异步的loader时,可以使用this.callback
// makeloader/loaders/replaceLoader.js
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const options = loaderUtils.getOptions(this);
const callback = this.async();
setTimeout(() => {
const result = source.replace('dell', options.name);
callback(null,result);
}, 1000);
}
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
resolveLoader:{
modules:['node_modules','./loaders']
},
module: {
rules: [{
test: /\.js/,
use: [
{
loader: 'replaceLoader'
},
{
loader: 'replaceLoaderAsync',
options: {
name: 'lee'
}
}
]
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
如何编写一个Plugin
插件是一个类:
//plugins/copyright-webpack-plugin.js
class CopyrightWebpackPlugin {
constructor(options) {
console.log(options);
}
apply(compiler) {
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
console.log('compiler');
})
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
debugger;
compilation.assets['copyright.txt']= {
source: function() {
return 'copyright by dell lee'
},
size: function() {
return 21;
}
};
cb();
})
}
}
module.exports = CopyrightWebpackPlugin;
在webpack.config.js中使用插件,new插件的类:
const path = require('path');
const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new CopyRightWebpackPlugin()
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
Bundler 源码编写(模块分析)
读取项目的入口文件,然后去分析项目的代码。
npm install cli-highlight@2.0.0 -g //高亮显示代码
在运行命令时,后面加上|highlight,便可以看到高亮显示的代码。
node bundler.js | highlight
npm install @babel/parser@7.2.3 --save-dev
npm install @babel/traverse@7.2.3 --save-dev
通过 CreateReactApp 深入学习 Webpack 配置
全局安装create-react-app脚手架工具:
npm install -g create-react-app@3.3.1
初始化react项目my-app
create-react-app my-app
不要使用npx create-react-app my-app 的命令初始化项目,会报以下错误:A template was not provided. This is likely because you’re using an outdated version of create-react-app.Please note that global installs of create-react-app are no longer supported.
npm run eject // 弹射出隐藏的webpakc配置项,会生成config和scripts目录
npm run start // 启动命令
npm run build // 生成线上环境即生产环境的代码
Vue CLI 3 的配置方法及课程总结
安装vue cli 脚手架工具,其中封装了常用的配置项目,具体如何配置可参考 Vue 官网进行配置。
npm install -g @vue/cli
初始化vue项目my-project
vue create my-project
启动vue项目
npm run serve
产出vue项目
npm run build
基础知识学会,然后学会如何查询文档。