webpack 模块打包工具
一、安装webpack
npm install webpack -D
####1. 创建项目
我们在合适位置新建一个文件夹wpk-test,用于存放我们的项目。
命令行中定位到webpack-test文件夹下,输入以下命令进行项目的初始化:
npm init
这里,要求设置很多选项,可以按项目情况配置也可以不填直接回车。完成后,我们发现文件夹中增加了package.json
文件,它用于保存关于项目的信息
2.安装webpack-cli
我们在项目中本地安装webpack-cli:
npm install webpack-cli -D
二、打包
全局安装webpack时,
global
webpack xx.js
局部安装(推荐局部)
local
npx webpack xx.js
npx能帮助在寻找该文件下的webpack
用script脚本执行
"script":{
"build":"webpack"
}
用脚本运行会自动先寻找本地的webpack
三、 手动配置webpack
默认配置文件的名字 webpack.config.js
scripts脚本中命令可用run 执行。“webpack --config webpack.config.js” 配置 运行webpack的文件名 (指定配置文件)
1.基础配置
entry 入口文件 output输出文件
可以有多个入口文件, [name] 指代前面的 main sub
const path = require("path");
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
sub: './src/index.js'
},
output: {
publicPath: 'http://cdn.com.cn', //g给打包后引入html的js添加统一前缀
filename: '[name].js', //入口文件的打包会走这里
chunkFilename: '[name].chunk.js'//间接引入的文件打包会走这里,
path: path.resolve(__dirname, 'dist') //输出到dist
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>html 模版</title>
</head>
<body>
<div id='root'></div>
<script type="text/javascript" src="http://cdn.com.cn/main.js"></script>
<script type="text/javascript" src="http://cdn.com.cn/sub.js"></script>
</body>
</html>
performance:false 关闭性能提升的警告,
resolve 配置默认查询后缀
{
resolve:{
extensions:['js','jsx'] ,//当你引入模块时,如果省略掉后缀就会默认先去找js,然后查找是否存在jsx
//当配置过多,会过多查找,导致性能降低
mainFiles:['index','child'], //引入文件夹时,默认引入下面的文件
alias:{
@:path.resolve(_dirname,'./src') //配置别名
}
}
}
devtool配置
none 无配置,可以加快打包效率
eval 打包最快,性能最佳,会直接将业务代码当成eval()执行并映射也能定位到原文件,但当文件过大时,提示可能不全面
source-map 映射,会将打包后文件报错的等信息,映射到打包前的文件,而不是打包后的文件,方便查找定位错误 会在dist目录下生成 .map文件
inline-source-map 效果一样,但不会生成文件,会被文件转换为base64字符,放在main.js的底部
cheap-inline-source-map 没加cheap会定位精确到那个字符,会过多消耗性能,我们只需要定位到行即可
cheap-module-inline-source-map 没加module 是不会定位第三方模块等报错信息,加了module就会定位
cheap-module-eval-source-map 提示最好,性能也不错, 在开发环境推荐使用
chap-module-source-map 线上推荐使用
source-map 前面的前缀都可以任意组合使用,无关顺序
module.exports = {
devtool:'none'
}
webpackDevSever配置
这种打包是放在内存中而不会生成打包文件
我们可以简单在packge.json中使用 webpack --watch 来监听源文件的变化,然后自动更新打包,但是这样当你需要ajax的时候,这样就不行了,所以,我们需要借助webpackDevSever
安装 webpack-dev-server 开启本地服务
npm i webpack-dev-server -D
config.js
module.exports = {
...
devServer: {
contentBase: './dist', //开启服务器的文件
open: true, //自动打开浏览器
port: 8080,
overlay: true, //当检测出错误的时候会在浏览器出现弹出层
},
...
}
packge.json 添加启动命令
"scripts": {
"start": "webpack-dev-server",
},
webpack-dev-middleware 用node自己编写一个本地sever
安装
npm i webpack-dev-middleware -D
配置 node sever.js
这只是简单的完成了功能,修改后需要手动刷新,页面,自动需要更多的配置
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
// 在node中直接使用webpack
// 在命令行里使用webpack
const complier = webpack(config); //编译代码,文件发生改变,就会运行
const app = express();
app.use(webpackDevMiddleware(complier, {}));
app.listen(3000, () => {
console.log('server is running');
});
pakge.json
"scripts": {
"sever": "node sever.js",
},
HotModule Replacement 热模块更新
当我们修改页面时,并不想刷新页面,只想修改的模块部分刷新,这个时候就可以使用热更新
webpack.config.js
const webpack = require('webpack');
module.exports = {
...
devServer: {
contentBase: './dist', //开启服务器的文件
open: true, //自动打开浏览器
port: 8080,
hot: true, //开启热更新
hotOnly: true //即时热更新不执行,浏览器也不会刷新页面
},
plugins: [
new webpack.HotModuleReplacementPlugin() //热更新插件
],
...
}
xx.js
js实现需要添加下面的代码,来实现热更新效果,js需要监听更新,自己修改
if(module.hot) { //如果有热更新
//监听指定文件的更新,监听到了,就会执行回调函数
module.hot.accept('./xx.js', () => {
//对检测到更新后的处理
})
}
css-loader底层已经实现了热更新,vue, react框架中也自己实现了。
proxy代理
这个代理只对本地有效,只是方便开发的时候使用
devServer: {
contentBase: './dist',
open: true,
port: 8080,
proxy: {
'/react/api': {//当匹配到字符时代理到下面地址
target: 'https://www.baidu.com',
secure: false, //当转发到https时需要配置false
pathRewrite: { //将匹配到的字符重写
'header.json': 'demo.json'
},
changeOrigin: true, //有些网站对origin做了限制,开启即可
headers: {
host: 'www.dbaidu.com',
}
}
}
},
默认是无法对/
根路径进行代理 如果需要添加配置 index:’’ 或者false
多个路径代理
proxy:[{
context:['/auth','/api'],
target: 'http://localhost:3000',
}]
四、loader
什么是loader,loader就是打包处理模块,处理webpack不认识的资源
loader的配置位置
module: {
//loaderd的顺序,默认是从右向左执行 从下向上
// 单一loader时,use为一个对象,
// 当需要使用多个loader时,user是一个数组
rules: [{
test: /\.jpg$/,
use: {
loader: 'file-loader',
options:{
name:'[name].[ext]' , //打包后的文件名和后缀不变
outputPath:'images/' //打包到imgage文件下
}
}
},
{
test:/\.less$/,
use:[
'style-loader',
'css-loader', //@import 解析路径
'less-loader' // 将less转换成css
]
}
]
},
占位符palceholders
类似下面的[name] [ext]
{
test: /\.jpg$/,
use: {
loader: 'file-loader',
options:{
//paceholder 占位符
name:'[name].[ext]' //打包后的文件名和后缀不变
}
}
},
[contenthash] : 根据内容生成hash值,内容不变,hash值不改变
###(一)使用loader打包静态资源
1. 使用loader打包图片
file-loader打包图片
test: /\.(jpg|png|gif)$/,
use: {
loader: 'file-loader',
options:{
//paceholder 占位符
name:'[name].[ext]' , //打包后的文件名和后缀不变
outputPath: 'images/',
}
}
},
用url-lodaer优化file-loader
url-lodaer 会将图片打包成bas64字符放入js文件中,如果图片比较小,就能优化图片的加载,不需要在请求图片,
同时我们也可以设置超过一定大小的,图片,打包到dist包中,而不是打包成base64
module: {
rules: [{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240 //超过10kb打包成文件,小于打包到js中base64
}
}
}]
},
2.打包样式处理
style-loader //将css插入head标签底部
css-loader //处理css
less less-loader//处理less
node-sass sass-lodaer //处理sass
postcss-loader autoprefixer //添加浏览器前缀 需要在package.json 中配置browserslist
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 6"
],
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
]
}]
},
添加配置文件postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
处理引入的sass文件中引入其他sass的打包
示例
//b.scss文件
@import './a.scss'
正常情况下, 我们会先通过scss,在通过css编译,单这种再次引入的文件中的scss语法就有可能不经过sass-loader,直接css-lodaer,这时,我们需要配置importLoaders,来让文件中import的文件也通过其他loader
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
}]
},
css样式打包模块化 option配置modules:true
将scss打包成模块来使用,这样样式影响的就是局部,而不是全局
引用方式
从 import ''./index.scss" 全局引入
变成模块引入 import style from './index.scss';
css模块化使用示例
index.scss
body {
.imgStyle {
width: 150px;
height: 150px;
transform: translate(100px, 100px);
}
}
createImg.js
import avatar from './a.jpg';
import style from './index.scss';
function createImg() {
var img = new Image();
img.src = imgSrc;
img.classList.add(style.imgStyle);
var root = document.getElementById('root');
root.append(img);
}
export default createAvatar;
index.js
import imgSrc from './a.jpg';
import style from './index.scss';
import createImg from './createImg';
createImg();
var img = new Image();
img.src = imgSrc;
img.classList.add(style.imgStyle);
var root = document.getElementById('root');
root.append(img);
3. 对于字体文件的处理
例如iconfont,webpack对于eot ttf等文件结尾并不认识,所以需要使用lodaer来打包,这里使用file-loader
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
},
五、plugin 插件
plugin 有点像生命周期,会在webpack运行到某一时刻的时候,帮你干点事情
安装
npm i html-webpack-plugin clean-webpack-plugin -D
html-webpapck-plugin 会在打包文件中自动生成html文件,同时将打包后的js,css文件注入到 html中
clean-webpack-plugin 帮我们打包前先自动清除dist目录下的东西
plugin使用示例
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
...loader
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])],
}
六、 Babel处理Es6语法
安装
npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev
babel/core babel的核心库,帮助babel识别js代码转换成AST抽象语法树
babel-loader 只是类似作为webpack与babel直接通信的桥梁,并不能将es6语法转换成es5
@babel/preset-env 将es6的语法转换为es5
config
module.exports ={
...
module: {
rules: [
{ test: /\.js$/,
exclude: /node_modules/, //排除 与下面相反
include:path.resolve(_dirname,'../src'), //只有在src下的才执行
loader: "babel-loader" ,
options:{
"presets": ["@babel/preset-env"]
}
}
]
}
}
实现低版本语法兼容
npm install --save-dev @babel/polyfill
有些语法在低版本浏览器中不存在,这个时候,就需要这个包来自己实现,pormise,等语法
在js入口的顶部导入polyfill,以确保首先加载polyfill
import "@babel/polyfill";
这样还有个问题,这样会将一些没有用的到的,语法一起编译实现,会让打包文件变大
我们只想,实现用到的语法,没用到的不需要打包到文件中,(当配置usage,babel会默认引入,所以就不需要,自己再引入"@babel/polyfill")
添加配置项
module: {
rules: [
{ test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{
presets: [
[
"@babel/preset-env",
{useBuiltIns:'usage'}
]
]
}
}
]
}
这种编译注入,是全局变量,容易造成全局污染,不适合,写类库
类库的转换配置方式
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babek/runtime-corejs2 //如果没有默认安装,手动安装
这样,帮助转换低版本语法时,会以闭包的形式注入
module: {
rules: [
{ test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
}
]
]
}
}
]
}
config中的options,内容可以提取到根目录 .babelrc 文件中, config中的options就可以删除掉
例:
.babelrc
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false,
}
]
]
}
七、webpack高级概念
1.Tree Shaking (剔除模块中未引入的代码)
只支持 ES Module
将模块比作树,我只引入一部分枝叶,没有引入的部分,不需要打包
简单就是当我们引入一个模块,我们只希望引入导出的,而不是模块整个的代码
在webpack中 mode ‘development’ 下默认是没有Tree Shaking的, 如果需要使用tree Shaking 需要配置
但是已经不会删除未引入的代码,因为本地需要调试,但是在打包文件中会用注释提示你已使用的部分,
config
module: {
optimization: {
usedExports: true
}
}
而在mode 'production’下,默认是开启了, 所以无需上面的配置,单需要下面的,同时打包后的文件是会删除多余代码
package.json
{
"sideEffects": false, //表示所有文件都需要使用Tree Shaking
}
// 但是对于一些css等类似的没有导出的引入,tree Shaking就会判断为,未使用模块,因此打包会忽略
@import 'style.css'
import "@babel/polyfill";
// 但是我们确实需要这些文件,因此我们需要配置那些文件不需要Tree Shaking,
{
"sideEffects": ['*.css', '@babel/polyfill'],
}
2. webpack 区分dev 和 pro 来管理
webpack的配置我们需要区分线上和开发模式,但2者也存在着共同的代码所以,我们提取出来公共部分,通过merge来合并,
根目录创建 build 文件 下面创建 build/webpack.dev.js,build/webpack.dev.js build/webpack.prod.js
安装
npm install webpack-merge -D
用于合并模块内容
packge.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"
},
webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: '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'], {
root: path.resolve(__dirname, '../')
})
],
optimization: {
splitChunks: {
chunks: 'all'
}
},
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-source-map'
}
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',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig);
第二种写法
packge.json
"scripts": {
"dev-build": "webpack --config ./build/webpack.common.js",
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js" //传人production
},
webpack.prod.js
const prodConfig = {
....
}
module.exports = prodConfig;
webpack.dev.js
const devConfig = {
....
}
module.exports = devConfig;
webpack.common.js
const merge = require('webpack-merge');
const commomConfig = {
....
}
module.exports = (env) => {
if(env && env.production) {
return merge(commonConfig, prodConfig);
}else {
return merge(commonConfig, devConfig);
}
}
3. Code Splitting 代码分割
当文件比如引入一个库,我们并不需要库,和业务逻辑都打包到main.js ,这个时候我们使用代码分割,可以将同步引入的库,与我们的业务逻辑做分离
[异步引入库](###4.lazy loading(懒加载))
webpack4底层默认添加了 SplitChunksPlugin插件,所以可以直接使用
开启代码分割 当 splitChunks为空对象时或者只配置 all,默认是如下配置
{
......
optimization: {
splitChunks: {
chunks: "all",//async只对异步代码进行代码分割,如果需要同步改成initial,都需要all,同时如果是同步分割会走 到cacheGroups 》vendors的配置中
minSize: 30000, //判断引入的库是否大于30000字节也就是30kb,大于才做代码分割
maxSize:50000, //如果配置这一项,webpack会进一步尝试,再次分割成多个 最大50kb的文件,一般不配置
minChunks: 1, //被使用多少次后,才进行代码分割
maxAsyncRequests: 5, //同时加载的模块最多5个,超过5个就不会代码分割
maxInitialRequests: 3,//入口文件代码分割最多3个,超过就不会进行代码分割
automaticNameDelimiter: '~', //连接符
name: true,
cacheGroups: { //缓存组
vendors: { //当开启同步分割,webpack会如下配置会去检查引入的包是否在node_modules中,是否符合这个组
test: /[\\/]node_modules[\\/]/,
priority: -10,//值越大,优先级越高,当同时满足组,优先使用优先级高的组
filename:'vendor.js' //如果配置该项,那么分割的代码都会打包到vendor.js中
},
default: { //当不满足上面的组时,默认走这里
minChunks: 1,
priority: -20,
reuseExistingChunk: true,//如果一个模块已经被使用,就不在打包,而去使用已经打包后的文件
filenameL'common.js'
}
}
}
},
}
4.lazy loading(懒加载)
下面2中,webpack带的异步加载,方式,实现的只有当点击页面时,才会加载lodash这个库,实现懒加载
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash');
const element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
}
// function getComponent() {
// return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
// var element = document.createElement('div');
// element.innerHTML = _.join(['Dell', 'Lee'], '-');
// return element;
// })
// }
document.addEventListener('click', () =>{
getComponent().then(element => {
document.body.appendChild(element);
});
})
5. webpack打包分析
打包过程的描述
--profile --json > stats.json
"scripts": {
"build": "webpack --env.production --profile --json > stats.json --config ./build/webpack.common.js"
},
可以借助 http://webpack.github.io/analyse/ 通过上传stats.json文件来分析,(要科学上网=-=)
安装插件 webpack-bundle-analyzer
6. webpack 优化首屏
如何查看代码使用率
ctrl + shift + p 输入 show coverage
在首屏,中比如类似弹框等代码,开始并不需要,却要加载,会消耗首屏时间,这个时候,我们可以用webpack的写法
click.js
//提出不需要首屏加载的代码
function handleClick() {
const element = document.createElement('div');
element.innerHTML = 'hello;
document.body.appendChild(element);
}
export default handleClick;
index.js
document.addEventListener('click', () =>{
import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
func();
})
});
//webpackPrefetch: true webpack 的写法,当核心代码加载完后,空余时间自动加载该代码
// webpackPreload : true 同时加载,(不使用)
7.css的代码分割
在webpack中css会默认打包到js文件中,所以我们想单独生成css文件需要引入插件
安装
npm istall --save-dev mini-css-extract-plugin
改插件存在缺陷,不支持HMR(热更新),所以只用于线上打包,
css 压缩
npm istall --save-dev optimize-css-assets-webpack-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
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'
]
}]
},
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})
]
}
module.exports = merge(commonConfig, prodConfig);
如果希望不管哪个入口的js引入的css 都打包到指定文件
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true, //忽略掉一些默认配置
},
},
},
},
}
如果希望分入口切割
module.exports = {
entry: {
foo: path.resolve(__dirname, 'src/foo'),
bar: path.resolve(__dirname, 'src/bar'),
},
optimization: {
splitChunks: {
cacheGroups: {
fooStyles: {
name: 'foo',
test: (m, c, entry = 'foo') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
barStyles: {
name: 'bar',
test: (m, c, entry = 'bar') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
},
},
}
}
8.webpack与浏览器缓存
当我们加载一个页面时,浏览器会帮我们缓存一些静态资源,所以如果我们打包的都叫main.js ,那么假设,用户在浏览网站,这个时候我们更新内容,如果用户只是普通的刷新,那么同样都叫main.js,这个时候浏览器发现内存中有,就会直接使用缓存,并不会获取更新的内容,所以我们需要如下改变
webpack.prod.js
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
这样就可以根据内容是否变化,来改变hash值,
在老版本中可能会一直都改变
需要配置
webpack.common.js
optimization: {
runtimeChunk: {
name: 'runtime'
},
}
9.webpack shimming (垫片 解决代码或者打包兼容问题)
webpack 模块打包是区分模块的,所以模块与模块直接不存在耦合关系,例:
我在index.js 中引入了jquery, 和一个使用了jquery的库
banck.js
export function fnc() {
$('body').css('background',green)
}
index.js
import $ from jquery;
import {fnc} from banck.js
fnc()
这样写并不会正常运行,$并不会和banck.js有关联,所以需要引入
但是如果是在nodemodule 中,我们不可能去改源码吧,所以我们可以借用providePlugin来实现,当webpack发现$时,就会自动引入jquery
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
_join: ['lodash', 'join'] //代表lodash中的join方法, 实际效果 _.join
}),
],
在模块中this通常都指向模块本身,如果想把模块的this都指向window,按如下配置
安装
npm install imports-loader --save-dev
config.js
rules: [{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader'
}, {
loader: 'imports-loader?this=>window'
}]
},
]
八、一些实际运用
1.webpack 打包库
当我们打包一个库的时候,被人使用可能是 import ,也可能是require 也可能是script
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'root',
libraryTarget: 'umd' //通用,可以是require 也可以是import
}
}
有些时候,library
和 libraryTarget 是联动的,library生成一个叫library的变量, libraryTarget意思是挂载到哪里
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library',
libraryTarget: 'this'
}
}
有时候我们库里会引入一些其他库,当别人引入时,又再一次引入了库,就很有可能会导致打包出2份,所以们需要配置忽略,同时需要用户自己引入
{
externals:['lodash']
}
2.PWA的打包d配置progressive Web Application
当服务器挂掉时,访问页面一般会出现找不到,但是PWA就就能简单实现,浏览器缓存页面,当你服务器挂掉时,依旧可以访问,缓存的页面 (下面简单的了解一下)
安装
npm i workbox-webpack-plugin -D
const WorkboxPlugin = require('workbox-webpack-plugin');
plugins: [
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
],
index.js
console.log('hello');
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');
})
})
}
3. typeScript 的打包配置
安装
npm i ts-loader typescript --save-dev
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}]
},
在根目录创建 tsconfig.json
{
"compilerOpitons": {
"outDir": "./dist",
"module": "es6", //模块引入方式
"target": "es5", //打包后的语法
"allowJs": true, //允许引入js
}
}
查询安装对应的typescript包类型文件 地址
只要存在就能通过 @type/loadash 类似安装对应的typescript提示,这样使用库时会存在语法提示
4.webpack中Eslint配置
安装
cnpm i eslint babel-eslint eslint-loader -D
配置
{
devServer: {
overlay: true, //当检测出错误的时候会在浏览器出现弹出层
},
modle:{
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader', {
loader:'eslint-loader',
options:{
fix:true
},
force:'pre' //强制优先执行
}]
}
]
}
}
对eslint 配置 找到根目录下 的 .eslintrc.js
示例:
下面代码是在安装时默认选择使用Airbnb公司的代码规范, 如果不想配置webpack,可以在vscode编辑器中安装eslint插件,配合这个配置一样能实现eslint的功能,当出现报错提示时,如果感觉该规范,我不需要执行,复制代码提示中eslint()
中的片段复制到 规则rules中设置为0即可
module.exports = {
"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"react/prefer-stateless-function": 0,
"react/jsx-filename-extension": 0
},
globals: { //全局变量中禁止被覆盖
document: false
}
};
5.webpack打包速度优化
1.跟上技术的迭代(node,npm,yarn)
2.在尽可能少的模块上应用loader
3.尽量使用官方插件,尽可少使用插件
4.resolve参数合理配置
5.使用DllPlugins 提高打包速度
正常情况下,当我们引用一个库的时候,我我们打包编译时,每次都会去分析引入的库,当库引入的越多,时间就会越慢,单其实,库并不需要每次都编译,所以我们想库只打包一次,后面就都用这个打包后的库,
新建一个webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: { //安装使用的库,可以分开也可以都放在vendors
vendors: ['lodash'],
react: ['react', 'react-dom'],
jquery: ['jquery']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
library: '[name]' // 将入口名作为全局变量暴露出去,
},
plugins: [
new webpack.DllPlugin({ // 生成对应打包库的映射文件,当打包时,引入库的时候会先去查看是否有对应的映射,没有才去node_modlues
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manifest.json'),
})
]
}
webpack.common.js
const path = require('path');
const fs = require('fs');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
//因为我是分开生成的dll,所以需要遍历生成plugins
const plugins = [];
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
if(/.*\.dll.js/.test(file)) {
//将库打包后,我们还需要手动在index.html中引入,下面的插件就起到添加静态资源的作用
plugins.push(new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if(/.*\.manifest.json/.test(file)) {
//当编译到引入库时,会先去查看是否有对应的映射文件,没有才去node_modlues中解析
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
module.exports = {
...
plugin
...
}
package.json
添加一行命令用于打包前预先打包库文件,如果库没有更新,就无需再次执行
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js",
"dll": "webpack --config ./build/webpack.dll.js"
},
6.控制包文件大小 (tree Shakiing)
- thread-loader,parallel-webpak,happypack 多进程打包
- 合理使用sourceMap
- 结合stats分析打包结果
- 开发环境内存编译
- 开发饭局无用插件剔除
6.webpack多页面打包
如今框架vue,react等都是单页应用,如何变成多页呢
如图index.html为模板页面,detail,index,list 分别为一个页面(手动使用react库),
打包成多页,实际就是生成多个入口,同时使用hrmlWebpackPlugin 生成多个html
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: /\.jsx?$/,
include: path.resolve(__dirname, '../src'),
use: [{
loader: 'babel-loader'
}]
}, {
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: '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