webpack4优化学习

版权声明: https://blog.csdn.net/weixin_33768153/article/details/84999236

libraryTarget 和 library

当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们

  • output.libraryTarget 配置以何种方式导出库
  • output.library 配置导出库的名称 output.libraryTarget 是字符串的枚举类型,支持以下配置

var (默认)

编写的库将通过var被赋值给通过library指定名称的变量。

index.js

module.exports =  {
    add(a,b) {
        return a+b;
    }
}

bundle.js

var calculator=(function (modules) {}({})

index.html

    <script src="bundle.js"></script>
    <script>
        let ret = calculator.add(1,2);
        console.log(ret);
    </script>

commonjs

编写的库将通过 CommonJS 规范导出。

导出方式

exports["calculator"] = (function (modules) {}({})

使用方式

require('npm-name')['calculator'].add(1,2);

npm-name是指模块发布到 Npm 代码仓库时的名称

commonjs2

编写的库将通过 CommonJS 规范导出。

导出方式

module.exports = (function (modules) {}({})

使用方式

require('npm-name').add();

在 output.libraryTarget 为 commonjs2 时,配置 output.library 将没有意义。

this

编写的库将通过 this 被赋值给通过 library 指定的名称,输出和使用的代码如下:

导出方式

this["calculator"]= (function (modules) {}({})

使用方式

this.calculator.add();

window

编写的库将通过 window 被赋值给通过 library 指定的名称,即把库挂载到 window 上,输出和使用的代码如下:

导出方式

window["calculator"]= (function (modules) {}({})

使用方式

window.calculator.add();

global

编写的库将通过 global 被赋值给通过 library 指定的名称,即把库挂载到 global 上,输出和使用的代码如下:

导出方式

global["calculator"]= (function (modules) {}({})

使用方式

global.calculator.add();

DLL

.dll为后缀的文件称为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据

  • 把基础模块独立出来打包到单独的动态连接库里
  • 当需要导入的模块在动态连接库里的时候,模块不能再次被打包,而是去动态连接库里获取
  • dll-plugin

定义Dll

  • DllPlugin插件: 用于打包出一个个动态连接库
  • DllReferencePlugin: 在配置文件中引入DllPlugin插件打包好的动态连接库
const path=require('path');
const DllPlugin=require('webpack/lib/DllPlugin');
module.exports={
    entry: {
        react:['react','react-dom']
    },// 把 React 相关模块的放到一个单独的动态链接库
    output: {
        path: path.resolve(__dirname,'dist'),// 输出的文件都放到 dist 目录下
        filename: '[name].dll.js',//输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称
        library: '_dll_[name]',//存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
    },
    plugins: [
        new DllPlugin({
            // 动态链接库的全局变量名称,需要和 output.library 中保持一致
            // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
            // 例如 react.manifest.json 中就有 "name": "_dll_react"
            name: '_dll_[name]',
            // 描述动态链接库的 manifest.json 文件输出时的文件名称
            path: path.join(__dirname, 'dist', '[name].manifest.json')
        })
    ]
}
webpack --config webpack.dll.config.js --mode development

使用动态链接库文件

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
plugins: [
  new DllReferencePlugin({
    manifest:require('./dist/react.manifest.json')
  })
]
webpack --config webpack.config.js --mode development

html中使用

<script src="react.dll.js"></script>
<script src="bundle.js"></script>

HappyPack

  • 构建需要解析和处理文件,文件读写和计算密集型的操作太多后速度会很慢
  • Node.js 之上的 Webpack 是单线程模型
  • happypack 就能让Webpack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。
npm i happypack@next -D
const HappyPack = require('happypack');
    rules: [
    {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname, 'node_modules'),
    },
    {
        test: /\.css$/,
        // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
        use: ['happypack/loader?id=css']
    }
],
plugins: [
    new Happypack({
        //ID是标识符的意思,ID用来代理当前的happypack是用来处理一类特定的文件的
        id: 'js',
        use: [{
            loader: 'babel-loader',
            //options=query都是向插件传递参数的
            options: {
                presets: [["@babel/preset-env", { modules: false }], "@babel/preset-react"],
                plugins: [
                    ["@babel/plugin-proposal-decorators", { "legacy": true }],
                    ["@babel/plugin-proposal-class-properties", { "loose": true }],
                ]
            }
        }]
    }),
    new Happypack({
        //ID是标识符的意思,ID用来代理当前的happypack是用来处理一类特定的文件的
        id: 'css',
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
        threads: 4,//你要开启多少个子进程去处理这一类型的文件
        verbose: true//是否要输出详细的日志 verbose
    })
]

CDN

CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。

cnd

  • HTML文件不缓存,放在自己的服务器上,关闭自己服务器的缓存,静态资源的URL变成指向CDN服务器的地址
  • 静态的JavaScript、CSS、图片等文件开启CDN和缓存,并且文件名带上HASH值
  • 为了并行加载不阻塞,把不同的静态资源分配到不同的CDN服务器上

使用缓存

  • 由于 CDN 服务一般都会给资源开启很长时间的缓存,例如用户从 CDN 上获取到了 index.html 这个文件后, 即使之后的发布操作把 index.html 文件给重新覆盖了,但是用户在很长一段时间内还是运行的之前的版本,这会新的导致发布不能立即生效 解决办法
  • 针对 HTML 文件:不开启缓存,把 HTML 放到自己的服务器上,而不是 CDN 服务上,同时关闭自己服务器上的缓存。自己的服务器只提供 HTML 文件和数据接口。
  • 针对静态的 JavaScript、CSS、图片等文件:开启 CDN 和缓存,上传到 CDN 服务上去,同时给每个文件名带上由文件内容算出的 Hash 值
  • 带上 Hash 值的原因是文件名会随着文件内容而变化,只要文件发生变化其对应的 URL 就会变化,它就会被重新下载,无论缓存时间有多长。
  • 启用CDN之后 相对路径,都变成了绝对的指向 CDN 服务的 URL 地址

域名限制

  • 同一时刻针对同一个域名的资源并行请求是有限制
  • 可以把这些静态资源分散到不同的 CDN 服务上去
  • 多个域名后会增加域名解析时间
  • 可以通过在 HTML HEAD 标签中 加入<link rel="dns-prefetch" href="">去预解析域名,以降低域名解析带来的延迟

接入CDN

要给网站接入 CDN,需要把网页的静态资源上传到 CDN 服务上去,在服务这些静态资源的时候需要通过 CDN 服务提供的 URL 地址去访问

    output: {
        path: path.resolve(__dirname, 'dist'),
+        filename: '[name]_[hash:8].js',
+        publicPath: 'http://img.qiniu.cn'
    },

Tree Shaking

Tree Shaking 可以用来剔除JavaScript中用不上的死代码。它依赖静态的ES6模块化语法,例如通过importexport导入导出。

普通情况

calc.js

export function add(a,b){
   return a + b;
}
export function minus(a,b){
   return a - b;
}

index.js

import {add} from './calc';
add(1,2)

calc.js

export function add(a,b){
   return a + b;
}

由于只用到了 calc.js 中的 add,所以剩下的都被 Tree Shaking 当作死代码给剔除了

不要编译ES6模块

  • 要让 Tree Shaking 正常工作的前提是交给 Webpack 的 JavaScript 代码必须是采用 ES6 模块化语法的
  • 对于 module.export Webpack 无法分析出哪些代码可以剔除
  • "modules": false 的含义是关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法。
    use:[{
        loader: 'babel-loader',
            options: {
+                presets:[['@babel/preset-env',{modules: false }],'@babel/preset-react']
        }
    }]

显示未使用的导出实例

npx webpack --display-used-exports

剔除用不上的代码

  • Webpack只是分析出了哪些函数用上了哪些没用上,要剔除用不上的代码还得经过UglifyJS去处理
webpack --display-used-exports --optimize-minimize

启用压缩

optimization: {
      minimizer: [
             new UglifyJsPlugin({
                 cache: true,//启动缓存
                 parallel: true,//启动并行压缩
                 //如果为true的话,可以获得sourcemap
                 sourceMap: true // set to true if you want JS source maps
             }),
             //压缩css资源的
             new OptimizeCSSAssetsPlugin({})
         ]
}

深度分析

webpack-deep-scope-analysis-plugin

import _ from 'lodash-es';
//加法
function isArray(value) {
    return _.isArray(value);
}
//减法
function add(a, b) {
    return a + b + _.isArray([]);
}
export {
    isArray,
    add
}
const WebpackDeepScopeAnalysisPlugin = require('webpack-deep-scope-plugin').default;

module.export = {
  plugins: [
    ...,
    new WebpackDeepScopeAnalysisPlugin(),
  ],
}

提取公共代码

为什么需要提取公共代码

大网站有多个页面,每个页面由于采用相同技术栈和样式代码,会包含很多公共代码,如果都包含进来会有问题

  • 相同的资源被重复的加载,浪费用户的流量和服务器的成本;
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。 如果能把公共代码抽离成单独文件进行加载能进行优化,可以减少网络传输流量,降低服务器成本

如何提取

提取公共代码

pageA.js

import utility1 from './utility1';
import utility2 from './utility2';
import $ from 'jquery';

pageB.js

import utility2 from './utility1';
import utility3 from './utility2';
import $ from 'jquery';

pageC.js

import utility2 from './utility3';
import utility3 from './utility1';
import $ from 'jquery';
    entry: {
        pageA: './src/pageA',
        pageB: './src/pageB',
        pageC: './src/pageC'
    },
    output: {
        path: path.resolve(__dirname,'dist'),
        filename: '[name].js'
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                // 不同页面之间的公用模块
                commons: {
                    chunks: "initial",
                    minChunks: 2,//最小重复使用的次数
                    minSize: 0 //最小提取字节数
                },
                // 第三方模块
                vendor: {
                    test: /node_modules/,
                    chunks: "initial",
                    name: "vendor",
                }
            }
        }
    }

    plugins:[
       new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'pageA.html',
            chunks: ['pageA'],
            minify: {
                removeAttributeQuotes: true
            }
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'pageB.html',
            chunks: ['pageB'],
            minify: {
                removeAttributeQuotes: true
            }
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'pageC.html',
            chunks: ['pageC'],
            minify: {
                removeAttributeQuotes: true
            }
        })
    ]

开启 Scope Hoisting

Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快, 它又译作 “作用域提升”,是在 Webpack3 中新推出的功能。

  • 代码体积更小,因为函数申明语句会产生大量代码
  • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小

插件配置

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
module.exports = {
  resolve: {
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};

代码

hello.js

export default 'Hello';

index.js

import str from './hello.js';
console.log(str);

输出的结果main.js

var n = name = "zfpx";
  console.log(n)

函数由两个变成了一个,hello.js 中定义的内容被直接注入到了 main.js 中

动态导入和懒加载

用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载 在给单页应用做按需加载优化时,一般采用以下原则:

  • 对网站功能进行划分,每一类一个chunk
  • 对于首次打开页面需要的功能直接加载,尽快展示给用户
  • 某些依赖大量代码的功能点可以按需加载
  • 被分割出去的代码需要一个按需加载的时机

动态导入和懒加载

handler.js

module.exports=function () {
    alert('你点我啦!');
}

index.js

document.querySelector('#clickBtn').addEventListener('mouseover',() => {
    import('./handler').then(clickMe => {
        window.clickMe=clickMe.default;
    });
});

html

<div id="clickBtn" onclick="clickMe()">弹框</div>

react-router4 路由懒加载

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route} from 'react-router-dom';
import Bundle from './Bundle';
let LazyAbout=(props) => (<Bundle {...props} load={()=>import('./About')}/>)
let Home=() => <div>Home</div>
ReactDOM.render(
<Router>
    <div>
      <Route path="/" component={Home} />
      <Route path="/about" component={LazyAbout}/>
    </div>
</Router>,document.getElementById('root'));

Bundle

import React from 'react';
export default class Bundle extends React.Component{
    state={Mod: null}
    componentWillMount() {
        this.props.load().then(mod=>this.setState({Mod: mod.default? mod.default:mod}));
    }
    render() {
        let Mod=this.state.Mod;
        return Mod&&<Mod  {...this.props}/>;
    }
}

About

import React from 'react';
export default props => <div>About</div>

猜你喜欢

转载自blog.csdn.net/weixin_33768153/article/details/84999236