1.0.2 • Published 4 years ago

kgm-webpack-builder v1.0.2

Weekly downloads
3
License
ISC
Repository
-
Last release
4 years ago

kgm-webpack-builder

介绍

webpack构建通用配置包

软件架构

软件架构说明

安装教程

  1. xxxx
  2. xxxx
  3. xxxx

使用说明

  1. xxxx
  2. xxxx
  3. xxxx

参与贡献

  1. Fork 本仓库
  2. 新建 Feat_xxx 分支
  3. 提交代码
  4. 新建 Pull Request

构建配置包说明

构建配置包设计

  1. 通过多个配置文件管理不同环境的webpack配置
    • 基础配置: webpack.base.js
    • 开发环境: webpack.dev.js
    • 生产环境: webpack.prod.js
    • SSR环境: webpack.ssr.js
  2. 抽离成一个npm包统一管理
    • 规范:Git日志、readme、ESLink、Semer规范
    • 质量:冒烟测试、单元测试、测试覆盖率和CI
  3. 通过webpack-merge组合配置
    merge = require('webpack-merge)
    merge(
        {a: [1], b: 5, c: 20},
        {a: [2], b: 3, d: 20},
    )
    => {a: [1, 2], b: 3, c: 20, d: 20}
    // 合并配置
    module.exports = merge(baseConfig, devConfig)

功能模块设计

基础配置:webpack.base.js

  1. 资源解析
    • 解析ES6
      • npm i @babel/core @babel/preset-env babel-loader -D // 用来编译ES6
      • npm i -D @babel/plugin-proposal-class-properties // 是用来编译class类的
    • 解析React
      • npm i -S react react-dom @babel/preset-react // 用来编译react
    • 解析CSS
      • npm i -D css-loader style-loader // 解析css
    • 解析Less
      • npm i -D less less-loader // 解析less
    • 解析图片
      • npm i -D file-loader
      • npm i -D url-loader // 也可以处理字体的图片,可以设置较小资源自动base64
    • 解析字体
  2. 样式增强
    • css前缀补齐
      • npm i postcss-loader autoprefixer -D
    • css px转rem
      • npm i px2rem-loader -D
      • npm i lib-flexible -S
  3. 目录清理
    • npm i clean-webpack-plugin -D
  4. 多页面打包
    • npm i -D glob
  5. 命令行信息显示优化
    • npm i -D friendly-errors-webpack-plugin
  6. 错误捕获和处理
  7. css提取成一个单独的文件

开发环境:webpack.dev.js

  1. 代码热更新
    • css热更新
    • js热更新
  2. sourcemap

生产环境: webpack.prod.js

  1. 代码压缩
  2. 文件指纹
  3. Tree Shaking
  4. Scope Hositing
  5. 速度优化: 基础包CDN
    • npm i -D html-webpack-externals-plugin
  6. 体积优化: 代码分隔
    • npm install --save-dev @babel/plugin-syntax-dynamic-import
    • 修改文件:.babelrc
      { "plugins": [ "@babel/plugin-syntax-dynamic-import" ] }

SSR环境: webpack.ssr.js

  1. output 的 libraryTarget 设置
  2. css 解析 ignore

tools环境:webpack.tiils.js

  1. 用于针对性打包的配置

    • npm i terser-webpack-plugin -S

目录结构设计

  1. 结构如下:(test:放置测试代码, lib:放置源代码)
    /test
    /lib
        - webpack.base.js
        - webpack.dev.js
        - webpack.prod.js
        - webpack.ssr.js
    README.md
    CHANGELOG.md
    .eslinrc.js
    package.json
    index.js

配置开始

完善文件内容

  1. 项目初始化:npm init -y
  2. 创建文件夹/文件
    • webpack.base.js
    • webpack.dev.js
    • webpack.prod.js
    • webpack.ssr.js
  3. 安装插件:npm i webpack-merge -D

使用ESLint规范构建脚本

  1. 使用 eslint-config-airbnb-base
    • npm i eslint babel-eslint eslint-config-airbnb-base -D
    • eslint --fix 可以自动处理空格
  2. 创建文件: .eslintrc.js
    module.exports = {
        "parser": "babel-eslint",
        "extends": "airbnb-base",
        "env": {
            "browser": true,
            "node": true
        },
        "rules": {
            <!-- "global-require": 0 ,
            "no-console":"off"
            "semi": 0,              // 结尾不加分号
            'generator-star-spacing': 'off',
            "no-tabs":"off",
            "indent": "off", // h忽略换行, tab时4个空格才对["error", 4]
            "linebreak-style": ["off", "windows"],  // 回车和换行符问题
            "no-console": "off",        // 允许console.log
            "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], // js文件中可以写jsx语法 -->
        }
    }
  3. 修改文件:package.json
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "eslint": "eslint ./lib --fix"
    }
  4. 运行检查eslint:npm run eslint

package.json文件

- 要注意依赖和非依赖的位置
```
{
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "eslint": "eslint ./lib --fix",
        "dev": "webpack-dev-server --config ./lib/webpack.dev.js --open",
        "build": "webpack --config ./lib/webpack.prod.js"
    },
    "devDependencies": {
        "eslint": "^6.8.0",
        "eslint-config-airbnb": "^18.0.1",
        "eslint-config-airbnb-base": "^14.0.0",
        "eslint-loader": "^3.0.3",
        "eslint-plugin-import": "^2.20.1",
        "eslint-plugin-jsx-a11y": "^6.2.3",
        "eslint-plugin-react": "^7.18.3",
        "babel-eslint": "^10.1.0"
    },
    "dependencies": {
        "webpack": "^4.41.6",
        "webpack-cli": "^3.3.11",
        "webpack-dev-server": "^3.10.3",
        "webpack-merge": "^4.2.2",
        "@babel/core": "^7.8.4",
        "@babel/plugin-proposal-class-properties": "^7.8.3",
        "@babel/plugin-syntax-dynamic-import": "^7.8.3",
        "@babel/preset-env": "^7.8.4",
        "@babel/preset-react": "^7.8.3",
        "autoprefixer": "^9.7.4",
        "babel-loader": "^8.0.6",
        "clean-webpack-plugin": "^3.0.0",
        "css-loader": "^3.4.2",
        "cssnano": "^4.1.10",
        "file-loader": "^5.1.0",
        "friendly-errors-webpack-plugin": "^1.7.0",
        "html-webpack-externals-plugin": "^3.8.0",
        "html-webpack-plugin": "^3.2.0",
        "less": "^3.11.1",
        "less-loader": "^5.0.0",
        "mini-css-extract-plugin": "^0.9.0",
        "optimize-css-assets-webpack-plugin": "^5.0.3",
        "postcss-loader": "^3.0.0",
        "px2rem-loader": "^0.1.9",
        "react": "^16.13.0",
        "react-dom": "^16.13.0",
        "style-loader": "^1.1.3",
        "url-loader": "^3.0.0"
    }
}
```

冒烟测试

冒烟测试执行

  1. 构建是否成功
  2. 每次构建完成的build目录是否由内容输出
    • 是否有js、css等静态资源文件
    • 是否有HTML文件
  3. 在示例项目里面运行构建,看看是否有报错
  4. 判断基本功能是否正常
    • 编写 mocha 测试用例
    • 是否有js、css等静态资源文件
    • 是否有HTML文件

开始 mocha 冒烟测试

  1. 在测试文件夹操作
    • 创建文件夹:./test/smoke
    • 创建文件:./test/smoke/index.js
    • 创建文件夹:./test/smoke/template
      • 在这里面拷贝一个项目
  2. 编辑文件:./test/smoke/index.js

    • 安装插件:npm i -D rimraf。 构建前用来删除dist目录
    const path = require('path')
    const webpack = require('webpack')
    const rimraf = require('rimraf')    // 删除文件的插件
    const Mocha = require('mocha')  // 编写测试用例的插件
    
    const mocha = new Mocha({
        timeout: '10000ms'
    })
    
    // 当前进程的工作目录
    process.chdir(path.join(__dirname, 'template')) // process.chdir() 方法变更 Node.js 进程的当前工作目录
    
    // 删除dist目录
    rimraf('./dist', () => {
        // 获取构建配置
        const prodConfig = require('../../lib/webpack.prod.js')
        // 运行配置
        webpack(prodConfig, (err, stats) => {
            // 运行失败
            if(err) {
                console.err(err);
                process.exit(2)
            }
            // 运行成功
            console.log(stats.toString({
                colors: true,
                modules: false,
                children: false
            }));
            // 加入测试用例
            console.log('webpack build success, begin run test');
            mocha.addFile(path.join(__dirname, 'html-test.js'))
            mocha.addFile(path.join(__dirname, 'css-js-test.js'))
            mocha.run()
        })
    })
  3. 运行当前编写的文件: node ./test/smoke/index.js

编写测试用例

  1. 安装插件:npm i mocha -D
  2. 新建文件:./smoke/html-test.js
    • 用来检查是否存在html文件:npm i -D glob-all
    const glob = require('glob-all')
    // 检测是否存在html文件
    describe('Checking generated html files', () => {
        it('should generate html files', (done) => {
            // 同步判断文件是否生成了出来
            const files = glob.sync([
                './dist/index.html',
                './dist/search.html',
            ])
            if (files.length > 0) {
                done()
            } else {
                throw new Error('no html files generate!')
            }
        })
    })
  3. 新建文件:./smoke/css-js-test.js
    • 用来检查是否存在css/js文件
    const glob = require('glob-all')
    // 检测是否存在html文件
    describe('Checking generated css js files', () => {
        it('should generate css js files', (done) => {
            // 同步判断文件是否生成了出来
            const files = glob.sync([
                './dist/js/index_*.html',
                './dist/js/search_*.html',
                './dist/css/index_*.css',
                './dist/css/search_*.css',
            ])
            if (files.length > 0) {
                done()
            } else {
                throw new Error('no css js files generate!')
            }
        })
    })

单元测试与测试覆盖率

单元测试描述

  1. 技术选型:mocha + chai
  2. 测试代码:descript, it, expect
  3. 测试命令:mocha add.test.js
    const expect = require('chai').expect
    const add = require('../src/add')
    descripe('use expect:src/add.js', () => {
        it('add(1,2) === 3', () => {
            expect(add(1, 2).to.equal(3))
        })
    })

单元测试接入

  1. 安装 mocha + chai
    • npm i mocha chai -D
  2. 新建test目录,并增加xxx.test.js测试文件
  3. 修改package.json文件
    "script": {
        "test": "./node_modules/.bin/_mocha"
    }
  4. 执行测试命令
    • npm run test
  5. 创建文件夹/文件
    • ./test/unit/webpack-base-test.js
    const assert = require('assert')
    describe('webpack.base.js test case', () => {
        const baseConfig = require('../../lib/webpack.base')
        it('entry', () => {
            assert.equal(baseConfig(true).entry.index.includes('/test/smoke/template/src/index/index.js'), true)
            assert.equal(baseConfig(true).entry.search.includes('/test/smoke/template/src/search/index.js'), true)
        })
    })
    • ./text.index
    const path = require('path')
    // 进入模板目录
    process.chdir(path.join(__dirname, 'smoke/template' ))
    describe('builder-webpack test case', () => {
        require('./unit/webpack-base-test')
        // ... require其他文件
    })
  6. 运行测试
    • npm run test

测试覆盖率(下面的代码测试失败)

  1. 安装插件: npm i -g istanbul
  2. 修改package.json文件
    "script": {
        "test": "istanbul cover ./node_modules/.bin/_mocha"
    }

持续集成和Travis-CI

  1. 优点:快速发现错误,防止分支大幅偏离主干(每次提交前都会运行测试)
  2. 核心措施:代码集成到主干前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
  3. 接入Travis-CI

发布到npm

  1. npm搜索是否存在此包
  2. 如果发布之前需要执行某件事情
    • 修改package.json文件
    "script": {
        "prepare": "webpack"
    }
  3. 发布:npm publish
  4. 如果本机第一次发布包(非第一次可忽略)
    • 在终端输入npm adduser,提示输入账号,密码和邮箱,然后将提示创建成功
  5. 非第一次发布包
    • 在终端输入npm login,然后输入你创建的账号和密码,和邮箱,登陆,结果上
  6. 如何撤销发布的包
    • 终端执行: npm unpublish
    • 删除某个版本: npm unpublish z-tool@1.0.0
    • 删除整个npm市场的包: npm unpublish z-tool --force
  7. 登录
    • npm login
  8. 注意
    • 发包前必须取消webpack.config.js中的文件监听模式
  9. 更新package.json的小版本号:npm version patch

webpack 分析

  1. 初级分析:使用webpack内置的stats(颗粒度比较粗)
    "script": {
        "build:stats": "webpack  --config ./lib/webpack.prod.js --json > stats.json"
    }
  2. 速度分析:使用npm i -D speed-measure-webpack-plugin
    • 分析整个打包总耗时
    • 每个插件和loader的耗时情况
    • 编辑:./lib.webpack.prod.js
      const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin') // 分析打包速度
      const smp = new SpeedMeasureWebpackPlugin()
      module.exports = smp.wrap(merge(baseConfig(false), prodConfig)) // module.exports = smp.wrap(/*webpack配置对象*/)
  3. 体积分析
    • 分析项目体积:使用npm i -D webpack-bundle-analyzer
    • 编辑:./lib.webpack.prod.js
    const WebpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;  // 分析打包体积
    // 插件
    plugins: [
        new WebpackBundleAnalyzer(),  // 分析打包体积
    ],

优化webpack构建

多进程/多实例:使用three-loader解析资源(效果不大,最终未使用)

  1. 安装:npm i -D thread-loader
  2. 修改:webpack.prod.js
    module: {
        rules: [
        {
            test: /\.js$/,
            use: [
            // { loader: 'thread-loader',  options: { workers: 3 } },  // 多进程打包
            'babel-loader',
            // 'eslint-loader', // 添加eslint
            ],
        },
        ],
    },

多进程/多实例“并行压缩 (效果不大,最终未使用)

  1. terser-webpack-plugin 开启parallel参数
  2. 修改:webpack.prod.js
    const TerserWebpackPlugin = require('terser-webpack-plugin');   // 多进程/多实例并行压缩
    optimization: {
        minimizer: [
            // new TerserWebpackPlugin({parallel: true})
        ]
    },

进一步分包:预编译资源模块(给基础库打包, 可以替代之前的optimization->splitChunks)

  1. 思路:
    • 将react,react-dom,redux,react-redux基础包和业务包打成一个文件
  2. 方法:
    • 使用DLLPlugin进行分包,
    • DllReferencePlugin对 manifest.json进行引用
  3. 创建文件:./lib/webpack.dll.js
  4. 修改文件:package.json
    "scripts": {
        "dll": "webpack --config ./lib/webpack.dll.js",
    },
  5. 后面没做了,自己看资料

缓存,提升二次构建速度

  1. 缓存思路
  2. babel-loader 开启缓存
    • 修改:webpack.base.js
    module: {
        rules: [
            {
            test: /\.js$/,
            use: ['babel-loader?cacheDirectory=true',
                // 'eslint-loader', // 添加eslint
            ],
            }
        ]
    }
  3. terser-webpack-plugin 开启缓存
    • 修改:webpack.prod.js
    const TerserPlugin = require('terser-webpack-plugin');
    module: {
        optimization: {
            minimizer: [new TerserPlugin({
                parallel: true, cache: true
            })],
        },
    }
  4. 使用cache-loader 或者 hard-source-webpack-plugin
    • 安装插件:npm i hard-source-webpack-plugin -D
    • 修改:webpack.prod.js
    const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');  // 用于打包缓存
    plugins: [
        new HardSourceWebpackPlugin(),    // 用于打包中的缓存
    ],

缩小构建目标

  1. 目的:尽可能少构建模块,比如:babel-loader 不解析node_modules
  2. 减少文件搜索范围
    • 优化 resolve.modules 配置(减少模块搜索层级)
    • 优化 resolve.mainField 配置
    • 优化 resolve.extensions 配置
    • 合理使用 alias

图片压缩(测试失败)

  1. 要求:基于 Node 库的imagemin 或者 tinypng API
  2. 使用:配置 image-webpack-loader
    • npm install image-webpack-loader --save-dev