webpack学习计划来啦
邵预鸿 Lv5

开始

webpack学习计划终于来了

一直感觉学webpack有些困难,拖了好久,面试了头条和腾讯都同时问到了webpack,webpack也该提上日程了


安装

1
npm i webpack webpack-cli -D

package.json添加

1
2
3
4
5
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.prod.js --env production --progress",
"start": "webpack serve --config webpack.config.env.js"
},

相关文档:https://www.webpackjs.com/guides/installation/#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6

常用名称

  • ‘[name]’ 当前文件名称
  • ‘[contenthash]’ 用于缓存问题,根据内容生成hash名称
  • ‘[ext]’ 文件后缀名
  • ‘[hash]’ 随机hash生成

入口Entry

  • 单个入口文件

    1
    2
    3
    {
    entry:'./app.js'
    }
  • 多个入口文件

    1
    2
    3
    4
    5
    6
    7
    {
    entry: {
    home: './home.js',
    about: './about.js',
    contact: './contact.js',
    },
    }

出口output

1
2
3
4
5
6
7
8
{
output:{
path: path.resolve(__dirname, 'dist'), //打包路径
filename: 'entry.js', //打包生成的文件
clean:true //打包前清除上一次的打包文件
publicPath:'/static' //打包后html文件里,添加公共的路径 如src = /static/app.js
}
}

懒加载

使用import语法,可以实现打包后项目需要的时候再加载,语法为import(‘模块名称’).then({deafult:模块名称}=>{})

  • import(‘/ * webpackChunkName: “jqueryA” * /‘) webpackChunkName指定懒加载js的文件名称
  • import(‘/ * webpackPrefetch: true * /‘) 预加载
1
2
3
4
5
6
7
8
9
10
11
12
13
(function(){
var template = `<div><input><button id="btn">获取input值</button></div>`;
document.body.innerHTML+=template;

//点击获取input
const btn = document.querySelector("#btn");
btn.onclick= function(){
import(/*webpackChunkName: "jqueryA"*/'jquery').then(({default:$})=>{
console.log($);
console.log("input的值是",$("input")[0].value)
})
}
})();

预加载与预获取

  • 预获取

    1
    import(/* webpackPrefetch: true */ './path/to/LoginModal.js');
  • 预加载

    1
    import(/* webpackPreload: true */ 'ChartingLibrary');

    测试中发现预加载和普通的import引入就没有多大的区别 了

  • 两者区别

    • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
    • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
    • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
    • 浏览器支持程度不同。

resolve

  • alias 创建 importrequire 的别名,来确保模块引入变得更简单

    1
    2
    3
    4
    5
    6
    resolve: {
    alias: {
    Utilities: path.resolve(__dirname, 'src/utilities/'),
    Templates: path.resolve(__dirname, 'src/templates/'),
    },
    },
  • extensions尝试按顺序解析这些后缀名。如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。

    1
    2
    3
    resolve: {
    extensions: ['.js', '.json', '.wasm'],
    },
  • mainFiles 解析目录时要使用的文件名。

    1
    2
    3
    resolve: {
    mainFiles: ['index','main'],
    },

    参考链接:https://webpack.docschina.org/configuration/resolve/#resolvemainfiles

Loader

  • asset 自动选择base64图还是文件
  • asset/source 文件输出
  • asset/inline base64图片输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
module: {
rules: [{
test: /jpe?g|png|gif$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024,
},
}, //当是图片时,大小决定data url还是file输出
generator: {
filename: 'static/[hash][ext]', //匹配到该规则 时,输出的文件路径
},
}]
},
}
  • postcss-loader 为css打包时,添加样式前缀 参考链接:https://webpack.docschina.org/loaders/postcss-loader/

    • 方法1 webpack.config.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    module.exports = {
    module: {
    rules: [
    {
    test: /\.css$/i,
    use: [
    'style-loader',
    'css-loader',
    {
    loader: 'postcss-loader',
    options: {
    postcssOptions: {
    plugins: [
    [
    'postcss-preset-env',
    {
    // 其他选项
    },
    ],
    ],
    },
    },
    },
    ],
    },
    ],
    },
    };

    postcss.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    module.exports = {
    plugins: [
    [
    'postcss-preset-env',
    {
    // 其他选项
    },
    ],
    ],
    };

    Loader 将会自动搜索配置文件。

    webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    module.exports = {
    module: {
    rules: [
    {
    test: /\.css$/i,
    use: ['style-loader', 'css-loader', 'postcss-loader'],
    },
    ],
    },
    };

    加大难度 ,为css添加前缀后,提出一个单独的css文件,并放置在css文件夹中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    module: {
    rules: [
    {
    test: /\.less$/,
    use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    'less-loader',
    'postcss-loader'
    ],
    },
    ]
    },

Mode

  • development 开发模式
  • production 生产模式

production 模式下,会自动清理console.log

缓存

普通缓存

1
2
3
4
5
6
 output: {
- filename: 'bundle.js',
+ filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},

利用[contenthash]实现针对文件内容生成的文件名,当文件内容修改,hash会不同,也就解决浏览器缓存的问题,可以即时刷新。当文件内容未修改,contenthash值相同,浏览器会使用缓存文件

缓存第三方文件

将第三方库(library)(例如 lodashreact)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资源,同时还能保证 client 代码和 server 代码版本一致。 这可以通过使用 SplitChunksPlugin 示例 2 中演示的 SplitChunksPlugin 插件的 cacheGroups 选项来实现。我们在 optimization.splitChunks 添加如下 cacheGroups 参数并构建:

1
2
3
4
5
6
7
8
9
10
11
12
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},

原文参考https://webpack.docschina.org/guides/caching/#output-filenames

打包CSS、js、img到各自文件总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry:'./app.js',
output:{
path: path.resolve(__dirname, 'dist'),
filename:'js/[name]-[contenthash].js',
clean:true
},
mode:'development',
module:{
rules:[
{test:/\.css$/,use:[MiniCssExtractPlugin.loader,'css-loader']},
{test:/\.(jpe?g|png)$/,type:'asset/resource',generator:{
filename:'img2/[name].[hash][ext]'
}},
{test:/\.html$/,use:'html-loader'} //如果html文件中存在图片,需要使用html-loader解析
]
},
resolve:{
alias:{
'@img':path.resolve(__dirname,'assets'),
'@css':path.resolve(__dirname,'css')
},
extensions:['.css','.js','.png','.jpeg','.jpg'],
mainFiles: ['index','1','global'],
},
plugins:[
new MiniCssExtractPlugin({
filename:'css/[name].css'
}),
new HtmlWebpackPlugin({
template:'./index.html',
title:'webpack html',
})
]
}
问题?使用htmlWebpackPlugin template时,使用到了图片,打包后图片不显示

devTool对比

是否可以追踪报错信息 备注
eval 构建:快速; 重建:最快,默认值 具有最高性能的开发构建的推荐选择。
source-map 具有高质量 SourceMap 的生产构建的推荐选择。打包会生成.map映射文件
inline-source-map 无ShourceMap文件,构建:最慢;重建:最慢。打包不会生成.map映射文件
none 生产环境用 具有最高性能的生产构建的推荐选择。 好像这个不行呢,使用报错

环境变量

1
2
"dev": "webpack server --config webpack.config.js --env development",
"prod":"webpack server --config webpack.config.js --env production",

webpack-merge合并webpack配置

  • webpack.config.js 入口配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const {merge}  = require('webpack-merge');
    const webpackComm = require('./webpack.config.comm');
    const webpackDev = require("./webpack.config.dev");
    const webpackProd = require('./webpack.config.production');
    module.exports =(e)=> {
    const mode = e.production?'production' : 'development';
    if(mode === 'production'){
    return merge(webpackComm,webpackProd,{mode})
    }else{
    return merge(webpackComm,webpackDev,{mode});
    }
    }
  • webpack.config.comm.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const {resolve} = require('path');
    module.exports = {
    entry:{
    app:'./app.js'
    },
    output:{
    path:resolve(__dirname,'dist'),
    filename:"js/[name].js",
    clean:true
    },


    }
  • webpack.config.dev.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const {resolve} = require("path");
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    module.exports = {
    module:{
    rules:[
    {
    test:/\.css$/,
    use:[MiniCssExtractPlugin.loader,'css-loader']
    },
    {
    test:/\.(jpe?g|png)$/,
    type:'asset/resource',
    generator:{
    filename:'[name]-[contenthash][ext]'
    }
    }
    ]
    },
    devtool:'eval',
    plugins:[
    new MiniCssExtractPlugin({
    filename:'[name].css'
    }),
    new HtmlWebpackPlugin({
    title:'webpack 打包的html'
    })
    ]
    }

  • webpack.config.prod.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const {resolve} = require("path");
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    module.exports = {
    module:{
    rules:[
    {
    test:/\.css$/,
    use:[MiniCssExtractPlugin.loader,'css-loader']
    },
    {
    test:/\.(jpe?g|png)$/,
    type:'asset/resource',
    generator:{
    filename:'img/[name]-[contenthash][ext]'
    }
    }
    ]
    },
    devtool:'eval',
    plugins:[
    new MiniCssExtractPlugin({
    filename:'css/[name].css'
    }),
    new HtmlWebpackPlugin({
    title:'webpack 打包的html'
    })
    ]
    }

    以上配置通过webpack.config.js入口配置文件,使用webpack-merge配置合并开发/生产 与公共配置合并成最后配置。

    此时在package.json中仅需要配置环境变量即可

    1
    2
    "build:prod": "webpack --config webpack.config.js --env production"
    "build:dev": "webpack --config webpack.config.js --env development"

devserver

几个重要参数
  • compress 为true 启用GZIP压缩

  • http2 使用 spdy 提供 HTTP/2 服务 HTTP/2 带有自签名证书:

  • https 默认情况下,开发服务器将通过 HTTP 提供服务。可以选择使用 HTTPS 提供服务

  • devMiddleware:{

    ​ writeToDisk: true,

    ​ } npx webpack server时,将内存中打包的代码写入到实体文件夹中

  • headers 为所有响应添加 headers:

    1
    2
    3
    4
    5
    devServer: {
    headers: {
    'X-Custom-Foo': 'bar',
    },
    },
  • hot 启用 webpack 的 热模块替换 特性:

  • liveReload 默认情况下,当监听到文件变化时 dev-server 将会重新加载或刷新页面。

  • open 告诉 dev-server 在服务器已经启动后打开浏览器。设置其为 true 以打开你的默认浏览器。

  • proxy 设置接口代理 假定项目中请求了以下接口

    1
    2
    http://www.shaoyuhong.cm/api/hello
    http://www.shaoyuhong.cm/axios/test
    • 最简单的一个代理 开发环境将请求地址为: http://www.shaoyuhong.cn/api/….

      1
      2
      3
      4
      5
      devserver:{
      proxy:{
      '/api':"http://www.shaoyuhong.cn"
      }
      }
    • 路径重写 开发环境将请求地址为: http://www.shaoyuhong.cn/api2/….

      1
      2
      3
      4
      5
      6
      7
      8
      devserver:{
      proxy:{
      '/api':{
      target:"http://www.shaoyuhong.cn",
      pathRewrite: { '^/api': '/api2' },
      }
      }
      }
    • 设置多个代理

      开发环境将请求地址为:http://127.0.0.1:18002/interface/…. && http://127.0.0.1:18002/shao/……

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {
      context:['/interface'],
      target:'http://127.0.0.1:18002'
      },
      {
      context:['/api'],
      target:'http://127.0.0.1:18002',
      pathRewrite:{
      '^/api':'/shao'
      }
      }
  • 默认情况下,将不接受在 HTTPS 上运行且证书无效的后端服务器。 如果需要secure:false,可以这样修改配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    //...
    devServer: {
    proxy: {
    '/api': {
    target: 'https://other-server.example.com',
    secure: false,
    },
    },
    },
    };
  • 默认情况下,代理时会保留主机头的来源,可以将 changeOrigin 设置为 true 以覆盖此行为。 在某些情况下,例如使用 name-based virtual hosted sites,它很有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    //...
    devServer: {
    proxy: {
    '/api': {
    target: 'http://localhost:3000',
    changeOrigin: true,
    },
    },
    },
    };

    参考链接:https://webpack.docschina.org/configuration/dev-server/#devserverproxy

插件

TerserWebpackPlugin
1
npm install terser-webpack-plugin --save-dev

该插件使用 terser 来压缩 JavaScript。

1
2
3
4
5
6
7
8
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};

参考文档:https://webpack.docschina.org/plugins/terser-webpack-plugin/

HtmlWebpackPlugin
1
npm install --save-dev html-webpack-plugin

HtmlWebpackPlugin 简化了 HTML 文件的创建,以便为你的 webpack 包提供服务

1
2
3
4
5
6
7
8
{
plugins: [
new HtmlWebpackPlugin({
template:'../index.html',
inject:true,//true,body,head 为true时,会在head中引入,并添加defer
chunk:['home','about'],//当前页面只展示某个chunk
})],
}

生成多个html文件 ??

1
2
3
4
5
6
7
8
9
10
const arr = ['shao','yu','hong'];
const htmls = arr.map(item=>{
return new HtmlWebpackPlugin({
template:'../index.html',
inject:'body',
filename:item+'.chunk.html',
chunk:[item],//当前页面只展示某个chunk
})
})
plugins: [...htmls],

内置变量

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
mini-css-extract-plugin

用于将style-loader生成的style标签样式,打包成css文件,使用link引入

外部扩展

防止将某些 包,如jQuery/Echarts…不打包在boundle中,而是使用script标签外部引入

例如,从 CDN 引入 jQuery,而不是把它打包:

index.html

1
2
3
4
5
<script
src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"
></script>

webpack.config.js

1
2
3
4
5
6
7
module.exports = {
//...
externals: {
jquery: 'jQuery', //jQuery是这个插件在window上挂载的什么就写什么 1.8.x的写法
jquery: '$', //3.x的写法
},
};

这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:

1
2
3
import $ from 'jquery'; //jquery来自于externals配置的属性名

$('.my-element').animate(/* ... */);

优化

离线运行

渐进式网络应用程序(progressive web application - PWA),是一种可以提供类似于 native app(原生应用程序) 体验的 web app(网络应用程序)。PWA 可以用来做很多事。其中最重要的是,在**离线(offline)**时应用程序能够继续运行功能。这是通过使用名为 Service Workers 的 web 技术来实现的

参考链接:https://webpack.docschina.org/guides/progressive-web-application/

添加 Workbox

我们通过搭建一个拥有更多基础特性的 server 来测试下这种离线体验。这里使用 http-server package:npm install http-server --save-dev。还要修改 package.jsonscripts 部分,来添加一个 start script: 这一步其实可以不用做,添加workbox-webpack-pulgin后,使用webpack直接起服务效果也是相同的

添加 workbox-webpack-plugin 插件,然后调整 webpack.config.js 文件:

1
npm install workbox-webpack-plugin --save-dev

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js',
},
plugins: [
new HtmlWebpackPlugin({
- title: 'Output Management',
+ title: 'Progressive Web Application',
}),
+ new WorkboxPlugin.GenerateSW({
+ // 这些选项帮助快速启用 ServiceWorkers
+ // 不允许遗留任何“旧的” ServiceWorkers
+ clientsClaim: true,
+ skipWaiting: true,
+ }),
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};

完成这些设置,再次执行 npm run build,看下会发生什么:

1
2
3
4
5
6
7
8
...
Asset Size Chunks Chunk Names
app.bundle.js 545 kB 0, 1 [emitted] [big] app
print.bundle.js 2.74 kB 1 [emitted] print
index.html 254 bytes [emitted]
precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js 268 bytes [emitted]
service-worker.js 1 kB [emitted]
...

现在你可以看到,生成了两个额外的文件:service-worker.js 和名称冗长的 precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.jsservice-worker.js 是 Service Worker 文件,precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.jsservice-worker.js 引用的文件,所以它也可以运行。你本地生成的文件可能会有所不同;但是应该会有一个 service-worker.js 文件。

所以,值得高兴的是,我们现在已经创建出一个 Service Worker。接下来该做什么?

注册 Service Worker

接下来我们注册 Service Worker,使其出场并开始表演。通过添加以下注册代码来完成此操作:

index.js

1
2
3
4
5
6
7
8
9
10
11
12
  import _ from 'lodash';
import printMe from './print.js';

+ if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('/service-worker.js').then(registration => {
+ console.log('SW registered: ', registration);
+ }).catch(registrationError => {
+ console.log('SW registration failed: ', registrationError);
+ });
+ });
+ }

再次运行 npm run build 来构建包含注册代码版本的应用程序。然后用 npm start 启动服务。访问 http://localhost:8080 并查看 console 控制台。在那里你应该看到:

1
SW registered

现在来进行测试。停止 server 并刷新页面。如果浏览器能够支持 Service Worker,应该可以看到你的应用程序还在正常运行。然而,server 已经停止 serve 整个 dist 文件夹,此刻是 Service Worker 在进行 serve。

Tree Shaking

  • 生成环境 usedExports

    usedExports 会将写的模块导入未使用的模块打包时丢弃

    1
    2
    3
    4
    mode:'production', 
    optimization: {
    usedExports: true,
    },

    1
    2
    3
    4
    5
    6
    import {    
    sum,
    cheng,
    sinData
    } from './until';
    console.log(sum(100,200));

    打包后只会生成

    1
    (()=>{"use strict";console.log(300)})();
  • sideEffects 将文件标记为 side-effect-free(无副作用)

    即,把某个文件标记为无副作用后,使用import导入的模块如果没有在代码中使用时,直接删除,包括CSS

    pagage.json中添加sideEffects:false

    1
    2
    3
    4
    5
    6
    {
    "name": "awesome npm module",
    "version": "1.0.0",
    "sideEffects": false, //false=>模块中任何文件无副作用,如果没有用到,可以随意删除,包括引入的CSS ;true=>有副作用,不可以随意删除
    "sideEffects": ["./src/some-side-effectful-file.js","./style.less","*.css"] //这个文件有副作用,涉及到这几处文件不能删除
    }
    • sideEffects === false时

      1
      2
      3
      4
      5
      6
      7
      import './style.less'
      import {
      sum,
      cheng,
      sinData
      } from './until';
      console.log(sum(100,200));

      打包后

      1
      (()=>{"use strict";console.log(300)})();
    • sideEffects === true时

      1
      (()=>{"use strict";var e,n,t,r,o,a,s,i,c,u,l,p,d,f,v={156:(e,n,t)=>{t.d(n,{Z:()=>i});var r=t(81),o=t.n(r),a=t(645),s=t.n(a)()(o());s.push([e.id,"ul,\nli {\n  list-style-type: none;\n}\nul {\n  display: flex;\n}\nul li {\n  flex: 1;\n  line-height: 2em;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n",""]);const i=s},645:e=>{e.exports=function(e){var n=[];return n.toString=function(){return this.map((function(n){var t="",r=void 0!==n[5];return n[4]&&(t+="@supports (".concat(n[4],") {")),n[2]&&(t+="@media ".concat(n[2]," {")),r&&(t+="@layer".concat(n[5].length>0?" ".concat(n[5]):""," {")),t+=e(n),r&&(t+="}"),n[2]&&(t+="}"),n[4]&&(t+="}"),t})).join("")},n.i=function(e,t,r,o,a){"string"==typeof e&&(e=[[null,e,void 0]]);var s={};if(r)for(var i=0;i<this.length;i++){var c=this[i][0];null!=c&&(s[c]=!0)}for(var u=0;u<e.length;u++){var l=[].concat(e[u]);r&&s[l[0]]||(void 0!==a&&(void 0===l[5]||(l[1]="@layer".concat(l[5].length>0?" ".concat(l[5]):""," {").concat(l[1],"}")),l[5]=a),t&&(l[2]?(l[1]="@media ".concat(l[2]," {").concat(l[1],"}"),l[2]=t):l[2]=t),o&&(l[4]?(l[1]="@supports (".concat(l[4],") {").concat(l[1],"}"),l[4]=o):l[4]="".concat(o)),n.push(l))}},n}},81:e=>{e.exports=function(e){return e[1]}},379:e=>{var n=[];function t(e){for(var t=-1,r=0;r<n.length;r++)if(n[r].identifier===e){t=r;break}return t}function r(e,r){for(var a={},s=[],i=0;i<e.length;i++){var c=e[i],u=r.base?c[0]+r.base:c[0],l=a[u]||0,p="".concat(u," ").concat(l);a[u]=l+1;var d=t(p),f={css:c[1],media:c[2],sourceMap:c[3],supports:c[4],layer:c[5]};if(-1!==d)n[d].references++,n[d].updater(f);else{var v=o(f,r);r.byIndex=i,n.splice(i,0,{identifier:p,updater:v,references:1})}s.push(p)}return s}function o(e,n){var t=n.domAPI(n);return t.update(e),function(n){if(n){if(n.css===e.css&&n.media===e.media&&n.sourceMap===e.sourceMap&&n.supports===e.supports&&n.layer===e.layer)return;t.update(e=n)}else t.remove()}}e.exports=function(e,o){var a=r(e=e||[],o=o||{});return function(e){e=e||[];for(var s=0;s<a.length;s++){var i=t(a[s]);n[i].references--}for(var c=r(e,o),u=0;u<a.length;u++){var l=t(a[u]);0===n[l].references&&(n[l].updater(),n.splice(l,1))}a=c}}},569:e=>{var n={};e.exports=function(e,t){var r=function(e){if(void 0===n[e]){var t=document.querySelector(e);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}n[e]=t}return n[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(t)}},216:e=>{e.exports=function(e){var n=document.createElement("style");return e.setAttributes(n,e.attributes),e.insert(n,e.options),n}},565:(e,n,t)=>{e.exports=function(e){var n=t.nc;n&&e.setAttribute("nonce",n)}},795:e=>{e.exports=function(e){var n=e.insertStyleElement(e);return{update:function(t){!function(e,n,t){var r="";t.supports&&(r+="@supports (".concat(t.supports,") {")),t.media&&(r+="@media ".concat(t.media," {"));var o=void 0!==t.layer;o&&(r+="@layer".concat(t.layer.length>0?" ".concat(t.layer):""," {")),r+=t.css,o&&(r+="}"),t.media&&(r+="}"),t.supports&&(r+="}");var a=t.sourceMap;a&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),n.styleTagTransform(r,e,n.options)}(n,e,t)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)}}}},589:e=>{e.exports=function(e,n){if(n.styleSheet)n.styleSheet.cssText=e;else{for(;n.firstChild;)n.removeChild(n.firstChild);n.appendChild(document.createTextNode(e))}}}},m={};function h(e){var n=m[e];if(void 0!==n)return n.exports;var t=m[e]={id:e,exports:{}};return v[e](t,t.exports,h),t.exports}h.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return h.d(n,{a:n}),n},h.d=(e,n)=>{for(var t in n)h.o(n,t)&&!h.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},h.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),h.nc=void 0,e=h(379),n=h.n(e),t=h(795),r=h.n(t),o=h(569),a=h.n(o),s=h(565),i=h.n(s),c=h(216),u=h.n(c),l=h(589),p=h.n(l),d=h(156),(f={}).styleTagTransform=p(),f.setAttributes=i(),f.insert=a().bind(null,"head"),f.domAPI=r(),f.insertStyleElement=u(),n()(d.Z,f),d.Z&&d.Z.locals&&d.Z.locals,console.log(300)})();

    参考链接: https://webpack.docschina.org/guides/tree-shaking/

Shimming 预置依赖

参考链接:https://webpack.docschina.org/guides/shimming/#granular-shimming

Shimming 预置全局变量

通过webpack new webpack.ProvidePlugin 暴露出全局变量,使得入口内部的js无论层级 都不需要再引入该模块,可以直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 const path = require('path');
+const webpack = require('webpack');

module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
+ plugins: [
+ new webpack.ProvidePlugin({
+ _: 'lodash',
$: 'jQuery'
+ }),
+ ],
};

如 a.js/b.js/c.js都可以使用使用$,无需再引入jquery

细粒度 Shimming

使用webpack后,this 不再指向window对象,代码中如果使用this对象访问window时,无法获取,this为undefined

为解决这个问题

你可以通过使用 imports-loader 覆盖 this 指向:

  • 安装 imports-loader

    1
    npm i imports-loader -D
  • 修改modules

    1
    2
    3
    4
    5
    6
    7
    8
    module: {
    + rules: [
    + {
    + test: require.resolve('./src/index.js'),
    + use: 'imports-loader?wrapper=window',
    + },
    + ],
    + },

    注意一点,使用imports-loader去修改this指针后,该js文件不能有其它类型的文件,如less/css等,否则将无法处理该文件

加载 Polyfills

安装

1
npm install --save-dev @babel/preset-env

webpack添加到module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3.22"
}
]
]
}
}
},
全局 Exports
作用

用于非npm包时三方插件,这个插件没有使用es6或者comm.js语法导出,可以使用webpack导出并使用es6 comm.js导入的形式使用,请看下面的例子

假定我下载了一个三方插件,这个插件是普通的js文件,无export 或者 module.exports

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//ani.js
class Ani{
constructor(status,success){
this.status = status;
this.success = success;
}
log(){
console.log(`提示信息,状态${this.status},成功数量:${this.success}`);
}
}
class CopyAni extends Ani{
constructor(status,success){
super(status,success)
}
}
const fileNanme = '这是ani.js'

问题来了,如何在我们的模块化js中 正常使用import /require导出fileName copyApi Ani这些函数、变量?

答案:**使用 exports-loader可以解决这个问题** https://webpack.docschina.org/loaders/exports-loader/

在webpack中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
test: require.resolve('./assets/ani.js'),
loader: 'exports-loader',
options: {
type: 'commonjs',
exports:
['fileNanme', 'multiple Ani AniFunction', 'multiple CopyAni CopyAniFunction','multiple 变量名/函数 导出的模块名'],
/*
最终生成的导出为
{
AniFunction: ƒ e(t,r)
CopyAniFunction: ƒ l(e,t)
fileNanme: "这是ani.js"
}
*/
},
},

现在我们在某个js文件中,可以正常导入使用

1
2
3
4
5
6
7
const methods = require('./ani');
const {AniFunction,CopyAniFunction,fileNanme} = methods;
const Ani1 = new AniFunction('成功AniFunction','100');
Ani1.log();
const Ani2 = new CopyAniFunction('失败CopyAniFunction','0');
Ani2.log();
console.log('导出的文件名:',fileNanme)

Web Workers

客户端原来和服务端可以相互通信,**原来以为,只能服务端 主动向客户端推送消息,并不能接收客户端数据**

客户端
1
2
3
4
5
6
7
8
9
10
11
12
const worker = new Worker(new URL('./server.js', import.meta.url));
const h4 = document.querySelector("#count");
setInterval(()=>{
console.log('我在问')
worker.postMessage({
question:
'你请求好了吗?',
});
},1000)
worker.onmessage = ({ data }) => {
h4.innerText = data;
};
服务端
1
2
3
4
5
6
7
8
9
10
var   i=0;
self.onmessage = ({ data: { question } }) => {
i++;
if(i<10){
self.postMessage(`Q:${question}----我还在请求中${i}`);
}else{
self.postMessage(`Q:${question}----请求成功!!!!!`);
}

};

web wokrder如果在webpack使用时,使用方式请参考:https://webpack.docschina.org/guides/web-workers/

打包图片资源

  • webpack5版本像url-loader,file-loder都是废弃不会直接使用的。我整了半天,虽然打包了图片,但是却打包了两次,并且无法显示图片了,
    如果想要使用这些废弃的旧功能,加上type: javascript/auto
  • esModule: false, 打包图片时需要关闭ES modules
  • outputPath:'static/img', 配置将资源文件输出的位置
1
2
3
4
5
6
7
8
9
10
11
12
module:{
rules:[{
test:/\.(png|jpe?g|gif)$/,
loader:'url-loader',
options:{
limit:30*1024,
esModule: false,
outputPath:'static/img',
},
type: 'javascript/auto'
}]
}

在webpack5中打包资源文件的修改

在 webpack 5 之前,通常使用:

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。

  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。

  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。

  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。url-loader与fileloader自动选择

    Rule.parser.dataUrlCondition:{maxSize:4*1024} 可以配置文件大小 的选择方式

当在 webpack 5 中使用旧的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模块时,你可能想停止当前 asset 模块的处理,并再次启动处理,这可能会导致 asset 重复,你可以通过将 asset 模块的类型设置为 'javascript/auto' 来解决。

generator:filename可以支持一个路径,将文件分类打包到不同的文件夹中

exclude 除开某个条件外的所有情况,都按此方法处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
test:/\.(png|jpe?g|gif)$/,
type:'asset/resource',
generator:{
filename: 'static/img/[hash:10][ext]',
}
},
{
exclude:/\.(png|jpe?g|gif|html|js|ts|css|less)$/,
type:'asset/resource',
generator:{
filename: 'static/media/[hash:10][ext]',
}
}

原文参考:https://webpack.docschina.org/guides/asset-modules/

output.assetModuleFilename:’shao/[hash] [ext]’ 可以将静态资源统一设置一个路径,当有generator中filename指定的路径时,优先于generator

TypeScript

参考链接:https://webpack.docschina.org/guides/typescript/

使用typescript时,如果安装其它插件,如jquery/react,需要同时安装插件的类型申明文件,可点击链接自行安装

插件类型安装地址:https://www.typescriptlang.org/dt/search?search=

使用npx tsc --init 可以快速生成typescript的配置文件tsconfig.json

其它事项

  • html-loader与html-webpack-plugin 有变量解析时,如

    1
    <title><%= htmlWebpackPlugin.options.title %></title>

    当配置了module:{loader:’html-loader’ }存在冲突,标题不能解析出来

  • ProvidePlugin 自动加载模块,而不必到处 import 或 require 。

    1
    2
    3
    4
    new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery'
    });
  • DefinePlugin DefinePlugin 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和生产模式的构建允许不同的行为非常有用

    1
    2
    3
    4
    5
    6
    7
    8
    new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
    VERSION: JSON.stringify('5fa3b9'),
    BROWSER_SUPPORTS_HTML5: true,
    TWO: '1+1',
    'typeof window': JSON.stringify('object'),
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    });

    值得注意的是,这里只能传【数字、字符串】。Array/object/booble/string需要使用json.stringify,属性名可以不用大写

webpack至此完结

2022/5/25

  • 本文标题:webpack学习计划来啦
  • 本文作者:邵预鸿
  • 创建时间:2022-01-10 18:11:09
  • 本文链接:/images/logo.jpg2022/01/10/webpack学习计划来啦/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!