webpack系列-优化

HappyPick

开启子线程打包,加快打包速度,使用id标识css

rules: [
      {
        test:/\.css/,
        use: 'Happypack/loader?id=css'
        
      },
      {
        test: /\.js$/,
        exclude:/node_modules/,
        include:path.resolve(__dirname,'src'),
        use: 'Happypack/loader?id=js'
      }
    ]
    
    // plugins
    new HappyPack({
      id:'css',
      use:['style-loader','css-loader']
    }),
    new HappyPack({
      id:'js',
      use: [{
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              '@babel/preset-react' // react jsx 
            ]
          }
      }]
    }),
复制代码

libraryTarget 和 library

当用 Webpack 去构建一个能够被其余模块导入使用的库时须要用到它们html

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

var(默认)

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

// 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(exports)

exports["calculator"] = (function (modules) {}({})
    
    require('npm-name')['calculator'].add(1,2);
复制代码

commonjs2

module.exports = (function (modules) {}({})
    
    require('npm-name').add();
复制代码

this

this["calculator"]= (function (modules) {}({})
    
    this.calculator.add();
复制代码

window

window["calculator"]= (function (modules) {}({})
    
    window.calculator.add();
复制代码

global

global["calculator"]= (function (modules) {}({})
    
    global.calculator.add();
复制代码

DLLPlugin

把基础模块独立出来打包到单独的动态链接库里.react

定义DLL(DLLPlugin,注意output.library和DLLPlugin的name须要保持一致)

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')
            })
        ]
    }
复制代码

使用动态连接库文

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
    plugins: [
      new DllReferencePlugin({
        manifest:require('./dist/react.manifest.json')
      })
    ]
复制代码

html中使用

须要将dll文件先提早引入webpack

<script src="react.dll.js"></script>
    <script src="bundle.js"></script>
复制代码

CDN

  • HTML文件不缓存,放在本身的服务器上,关闭本身服务器的缓存,静态资源的URL变成指向CDN服务器的地址web

  • 静态的JavaScript、CSS、图片等文件开启CDN和缓存,而且文件名带上HASH值npm

    • 带上 Hash 值的缘由是文件名会随着文件内容而变化,只要文件发生变化其对应的 URL 就会变化,它就会被从新下载,不管缓存时间有多长
  • 为了并行加载不阻塞,把不一样的静态资源分配到不一样的CDN服务器上json

    • 多个域名后会增长域名解析时间
    • 能够经过在 HTML HEAD 标签中 加入去预解析域名,以下降域名解析带来的延迟
  • 接入cdn(publicPath)promise

output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name]_[hash:8].js',
        publicPath: 'http://img.zhufengpeixun.cn'
    },
复制代码

Tree Shaking

Tree Shaking 能够用来剔除JavaScript中用不上的死代码。它依赖静态的ES6模块化语法,例如经过import和export导入导出。缓存

不要编译ES6模块

  • 要让 Tree Shaking 正常工做的前提是交给 Webpack 的 JavaScript 代码必须是采用 ES6 模块化语法的
  • "modules": false 的含义是关闭 Babel 的模块转换功能,保留本来的 ES6 模块化语法。
use:[{
        loader: 'babel-loader',
            options: {
               presets:[['@babel/preset-env',{modules: false }],'@babel/preset-react']
        }
    }]
复制代码

显示未使用的导出实例

npx webpack --display-used-exports
复制代码

剔除用不上的代码(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({})
         ]
}
复制代码

提取公共代码

大网站有多个页面,每一个页面因为采用相同技术栈和样式代码,会包含不少公共代码,若是都包含进来会有问题

optimization: {
        splitChunks: {
            cacheGroups: {
                commons: { // 页面之间的公共代码
                    chunks: "initial",
                    minChunks: 2,//最小重复的次数
                    minSize: 0//最小提取字节数
                },
                vendor: { // 第三方库
                    test: /node_modules/,
                    chunks: "initial", // 先抽离公共的第三方库
                    name: "vendor",
                }
            }
        }
    }
复制代码

Scope Hoisting

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

// hello.js
    export default 'Hello';
    // index.js
    import str from './hello.js';
    console.log(str);
    // main.js
    var n = name = "Hello";
    console.log(n)
复制代码

动态导入和懒加载(import导入返回一个promise)

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

  • 对网站功能进行划分,每一类一个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>
复制代码

热更新

配置hot

const webpack = require('webpack');
    module.exports = {
    entry:{
      main:'./src/index.js',
    },
    plugins: [
      // 该插件的做用就是实现模块热替换,实际上当启动时带上 `--hot` 参数,会注入该插件,生成 .hot-update.json 文件。
      new webpack.NamedModulesPlugin(), // 用于启动 HMR 时能够显示模块的相对路径
      new webpack.HotModuleReplacementPlugin(), // Hot Module Replacement 的插件
    ],
    devServer:{
      // 告诉 DevServer 要开启模块热替换模式
      hot: true,      
    }  
    };
复制代码

代码实现

import React from 'react';
    import { render } from 'react-dom';
    import App from './App';
    import './index.css';
    render(<App/>, document.getElementById('root'));
    
    // 只有当开启了模块热替换时 module.hot 才存在
    if (module.hot) {
      // accept 函数的第一个参数指出当前文件接受哪些子模块的替换,这里表示只接受 ./AppComponent 这个子模块
      // 第2个参数用于在新的子模块加载完毕后须要执行的逻辑
      module.hot.accept(['./App'], () => {
        // 新的 AppComponent 加载成功后从新执行下组建渲染逻辑
        let App=require('./App').default;  
        render(<App/>, document.getElementById('root'));
      });
    }
复制代码
相关文章
相关标签/搜索