• Webpack从原理到实战


    Webpack介绍

    官方定义

    一个现代的JavaScript应用程序的静态模块打包器。

    怎么理解打包动作?

    假如一个项目有 a.js b.js c.js ...(实际项目肯定很多)

    如果让各个js保持分离状态,要考虑的问题点有:

    • 各个js 之间的依赖关系

    • 每个资源分开请求加载,增加了性能开销

    出于种种原因,我们希望将多个JS文件揉合成一个,只加载一次,也不用考虑依赖关系。

    接着打包这个动作登场

    为什么要打包?

    • 逻辑多

    • 文件多

    • 项目复杂度增加了

    eg:

    • 原生JS没有类型校验的能力 于是有了 TypeScript

    • css 不够好用 有了 sass, less

    Webpack 不止打包,还有翻译能力(loader), 也可进行一些骚操作(plugin),loader 和 plugin 都是可插拔的,webpack没有规定你必须用什么,或者不能用什么。

    webpack 不仅强大 而且灵活

    Webpack的原理与背景

    Webpack产生的背景就是因为,随着前端项目的复杂度不断提高,文件数目,文件复杂度提高,传统的方式容易带来很多问题,比如命名冲突,各个模块之间的依赖关系需要人工去处理,以及后续代码的可维护性,代码耦合度等等一系列问题。

    模块化优点

    作用域封装

    重用性

    解除模块之间耦合, 提高系统的可维护性

    模块化发展

    详情见链接:

    [JavaScript早期实现模块化](JavaScript早期实现模块化 - lvzl - 博客园 (cnblogs.com))

    [AMD模块化](JavaScript AMD模块化规范 - lvzl - 博客园 (cnblogs.com))

    [CMD模块化](JavaScript CMD规范 - lvzl - 博客园 (cnblogs.com))

    [ES6模块化](JS模块化 - lvzl - 博客园 (cnblogs.com))

    打包工具

    gulp

    GRUNT

    webpack

    区别:前两者定位都是实现一个自动化构建工具,帮助程序员完成那些需要重复的操作。而webpack则是专注于打包。

    打包机制

    输出文件结构

    (function(module) {
        // 记录已经安装过的模块,防止二次加载,浪费时间
        var installedModules = {};
        // 加载模块的核心方法
        function __webpack_require__(moduleId){
            // SOME CODE
        }
        // 。。。
        return __webpack_require__(0); // 入口文件
    })([ /* 依赖模块的集合 */])
    

    核心方法

    function __webpack_require__(moduleId) {
        // check if module is in cache
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // create a new module (and put into cache)
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };
        // exe the module func
        modules[moduleId].call{
            module.exports,
                module,
                module.exports,
                __webpack_require__
        };
        // flag the module as loaded
        module.l = true;
        // return the exxports of the module
        return module.exports;
    }
    

    打包过程简述

    • 从入口文件开始,分析整个应用的依赖树

    • 将每个依赖模块包装起来,放到一个数组中等待调用

    • 实现模块加载的方法,并把它放到模块执行的环境中,确保模块间可以互相调用

    • 把执行入口文件的逻辑放在一个函数表达式中,并立即执行这个函数

    Webpack实战

    npm & 包管理器

    包管理器

    指可以便捷的让开发者获取、分发代码的工具,比如Vue,React就是在包管理器中管理的。

    npm

    npm init 初始化一个项目 npm init -y 所有选项都是y

    package.json

    {
      "name": "webpack-demo", // 包名称
      "version": "1.0.0", //版本号
      "description": "",
      "main": "index.js", // 包执行的入口文件
      "dependencies": {
        "loadsh": "0.0.4",
        "webpack": "^4.44.1",
        "webpack-cli": "^3.3.12"
      },
      "devDependencies": {},
      "scripts": { // 自定义脚本
        "test": "echo "Error: no test specified" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    
    

    npm '仓库' & '依赖'

    仓库就是npm包的站点

    // 设置仓库为淘宝镜像地址
    npm config set registry https://registry.npm.taobao.org
    
    // --save 会将安装包信息添加到package.json中的dependencies中,npm5之后默认会添加,之前的版本需要 --save参数才会
    npm install loadsh --save 
    
    // --save-dev 简写 -D 会将安装包信息添加到package.json中的devDependencies中 开发环境的依赖
    npm install loadsh --save-dev
    
    // 安装dependencies & devDependencies中的依赖信息
    npm install 
    
    // 只安装dependencies中的依赖信息
    npm install --only=prod 
    
    // 只安装devDependencies中的依赖信息
    npm install --only=dev 
    
    // 当我们把自己的npm包发布出去以后,别人通过npm install 安装时,只会安装dependencies中的依赖
    // dependencies: 与功能相关的依赖 
    // devDependencies: 开发时需要的一些辅助工具,比如eslint
    // 删除模块
    npm uninstall modulename  
    // 清除缓存
    npm cache clean --force 
    

    npm '语义化版本'

    ^3.4.1 -----> 3.X.X

    ~3.4.1 ------> 3.4.X

    3.4.1 ------> 3.4.1 特定版本

    npm '自定义工程脚本'

    package.json 的 script

    npm install的过程

    • 寻找包版本信息文件package.json, 依照进行安装
    • 查package.json中的依赖,并检查项目中其他的版本信息文件
    • 如果发现了新包,就更新版本信息文件

    demo

    初始化react-dom

    mkdir react-demo
    cd react-demo
    // 使用npm初始化,生成package.json
    npm init -y
    // 安装webpack相关模块
    npm install webpack@4.44.1 webpack-cli@3.3.12 webpack-dev-server
    npm install react react-dom
    // 安装处理JSX 和 ES6语法的模块
    npm install @babel/core @babel/preset-env @babel/preset-react 
    // 安装处理HTML文件的模块
    npm install html-webpack-plugin
    // 通过vscode 打开
    code .
    
    

    工程目录

    package.json

    {
      "name": "react-demo",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "dev": "webpack-dev-server --open",
        "build": "webpack --mode production"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "@babel/core": "^7.14.3",
        "@babel/preset-env": "^7.14.2",
        "@babel/preset-react": "^7.13.13",
        "babel-loader": "^8.2.2",
        "happypack": "^5.0.1",
        "html-webpack-plugin": "^4.5.2",
        "react": "^17.0.2",
        "react-dom": "^17.0.2",
        "terser-webpack-plugin": "4.2.3",
        "webpack": "^4.44.1",
        "webpack-bundle-analyzer": "^4.4.2",
        "webpack-cli": "^3.3.12",
        "webpack-dev-server": "^3.11.2"
      }
    }
    
    

    新建webpack.config.js

    const HtmlWebPackPlugin = require('html-webpack-plugin')
    const path = require('path')
    module.exports = {
        resolve: { // import模块时,在此配置过的文件名后缀,可以不写
            extensions: ['.jsx', '.js', '.json', '.mjs', '.wasm']
        },
        entry: path.resolve(__dirname, 'src/entry.jsx'), // 入口文件
        module: { // loader
            rules: [
                {
                    test: /.jsx?/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            babelrc: false,
                            presets: [
                                require.resolve('@babel/preset-react'),
                                [require.resolve('@babel/preset-env', {module: false})]
                            ]
                        }
                    }
                }
            ]
        },
        plugins: [ // plugin
            new HtmlWebPackPlugin({
                template: path.resolve(__dirname, 'src/index.html'),
                filename: 'index.html'
            })
        ],
        devServer: {
            hot: true
        }
    }s
    

    新建app.jsx模块

    import React from 'react'
    import ReactDom from 'react-dom'
    
    const App = () => {
        return (
            <h1>React Test</h1>
        )
    }
    export default App
    ReactDom.render(<App/>, document.getElementById('app'))
    

    新建入口文件entry.jsx

    import App from './app'
    if (module.hot) {
        module.hot.accept(error => {
            if (error) {
                console.error('HMR出BUG了');  
            }
        })
    }
    

    新建index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
    </html>
    

    打包

    node_modules.binwebpack //此处使用的局部 webpack 若已全局安装,直接使用webpack即可
    

    启动预览

    node_modules.binwebpack-dev-server
    node_modules.binwebpack-dev-server --open // 会直接在浏览器打开
    

    npm run dev

    Webpack与前端性能

    打包结果优化

    打包结果的体积越小越好

    借助一些插件完成打包结果的优化,比如去掉console,debugger,alert,以及一些无用代码。配置示例:

    const HtmlWebPackPlugin = require('html-webpack-plugin')
    const path = require('path')
    const webpack = require('webpack')
    const TerserPlugin = require('terser-webpack-plugin') // 处理压缩代码的模块
    // 打包结果分析器
    const WebpackAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 
    
    // uglifyjs-webpack-plugin 对es5的代码 压缩效果较好,而TerserPlugin是uglifyjs-es的一个分支,对ES6压缩效果较好
    module.exports = {
        optimization: {
            minimize: true,
            minimizer: [
                new TerserPlugin({
                    // 缓存,可以加快构建速度
                    parallel: 4, // 多进程并发 加快构建
                    terserOptions: {
                        compress: {
                            unused: true, // 没有用到的,剔除
                            drop_debugger: true, // debugger 剔除
                            drop_console: true, // console 剔除
                            dead_code: true // 无用代码 剔除
                        }
                    }
                })
            ]
        },
        resolve: {
            extensions: ['.jsx', '.js', '.json', '.mjs', '.wasm']
        },
        entry: path.resolve(__dirname, 'src/entry.jsx'),
        module: {
            rules: [
                {
                    test: /.jsx?/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            babelrc: false,
                            presets: [
                                require.resolve('@babel/preset-react'),
                                [require.resolve('@babel/preset-env', {module: false})]
                            ]
                        }
                    }
                }
            ]
        },
        plugins: [
            new HtmlWebPackPlugin({
                template: path.resolve(__dirname, 'src/index.html'),
                filename: 'index.html'
            }),
            new webpack.HotModuleReplacementPlugin(),
            new WebpackAnalyzer()
        ],
        devServer: {
            hot: true
        }
    }
    

    借助webpack-bundle-analyzer plugin对打包后的模块大小进行分析,从而进一步考虑还需要优化的文件

    构建过程优化

    • 首先我们先试着减少需要打包处理的文件,量少了,自然也就快了,比如解析不动的文件就不解析。jquery echarts

    npParse: /node_modules/(jquery.js)/

    exclude > include test

    • 但是当"量"已经没办法在减少了,那就只能想别的方法加快构建速度并发构建。借助HappyPack, thread-loader

    HappyPack

    const HappyPack = require('happypack')
    // 根据CPU的数量创建线程池
    const happyPackPool = HappyPack.ThreadPool({size: OscillatorNode.cpus().length})
    modules.exports = {
         plugins: [
            new HappyPack({
                ids: 'jsx',
                threads: happyPackPool,
                loaders: ['bable-loader'] // 需要注意happypack支持的loader
            })
        ]   
    }
    

    thread-loader

    module: {
            rules: [
                {
                    test: '/.js$/',
                    include: path.resolve('src'),
                    use: [
                        'thread-loader' // 放到其他loader之前
                    ]
                }
            ]
    }
    
    • sourceMap 生成优化,这应该十个很耗时间的操作

    • 找些本身就很快的loader替换普通的loader,比如fast-sass-loader

    tree-Shaking

    树—摇晃

    去除无用的代码(DCE)

    一种DCE的实现

    webpack 本身会分析ES6的module引入情况,去掉没有用到的,再借助terserPlugin进一步删除无用的模块。

    Webpack: 不止'pack'

    前端蓬勃发展的产物

    模块化打包方案

    工程化方案

  • 相关阅读:
    转载: SQLyog连接MySQL8 异常2059Authentication plugin 'caching_sha2_password' cannot be loaded解决方案
    linux 运维知识主页
    解决Xshell连接远程linux服务器,关闭Xshell程序对应的运行程序也相
    WPF布局控件
    WPF属性
    AutoMapper 四啥注意
    引用类型和值类型
    蠢鸟之xamarin.forms下面加载字体之二
    一头好奇的猫(庆军)之AES ECB模式C#要跟JAVA一致的话需要注意的
    加班
  • 原文地址:https://www.cnblogs.com/lvzl/p/14805986.html
Copyright © 2020-2023  润新知