webpack4搭建的一次尝试

1,598 阅读11分钟

目前支持typescript,react,jsx,less,eslint,prettier,husky等

具体的配置请查看:github.com/nvnvyezi/in…

如需搭配electron,可以查看:github.com/nvnvyezi/el…

好了,正文开始,这不是一个文章,只作为想自己搭建webpack的一个参考

概念

Entry:指定webpack开始构建的入口模块,从该模块开始构建并计算出直接或间接依赖的模块或者库

Output:告诉webpack如何命名输出的文件以及输出的目录

Loaders:由于webpack只能处理javascript,所以我们需要对一些非js文件处理成webpack能够处理的模块,比如sass文件

Plugins:Loaders将各类型的文件处理成webpack能够处理的模块,plugins有着很强的能力。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。但也是最复杂的一个。比如对js文件进行压缩优化的UglifyJsPlugin插件

Chunk:coding split的产物,我们可以对一些代码打包成一个单独的chunk,比如某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3及以前我们都利用CommonsChunkPlugin将一些公共代码分割成一个chunk,实现单独加载。在webpack4 中CommonsChunkPlugin被废弃,使用SplitChunksPlugin

webpack启动参数

  • color 输出结果带彩色,比如:会用红色显示耗时较长的步骤
  • profile 输出性能数据,可以看到每一步的耗时
  • progress 输出当前编译的进度,以百分比的形式呈现
  • display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
  • display-error-details 输出详细的错误信息

webpack4特性

  • Webpack4需要与webpack-cli一起使用
  • 新添了mode属性,
    • development
      • Process.env.NODE_ENV = development,需在启动时设置NODE_ENV=development,否则在编译过程中取不到该值
      • 默认开启以下插件,利用持久化缓存
      • namedChunksPlugin:以名称固化chunk id,
      • namemodulesPlugin:以名称固化module id
    • production
      • Process.env.NODE_ENV = production,同样需配置
      • 默认开启以下插件
      • SideEffectsFlagPlugin 和 UglifyJsPlugin 用于 tree-shaking
      • FlagDependencyUsagePlugin :编译时标记依赖
      • FlagIncludedChunksPlugin :标记子chunks,防子chunks多次加载
      • ModuleConcatenationPlugin :作用域提升(scope hosting),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度
      • NoEmitOnErrorsPlugin :在输出阶段时,遇到编译错误跳过
      • OccurrenceOrderPlugin :给经常使用的ids更短的值
      • SideEffectsFlagPlugin :识别 package.json 或者 module.rules 的 sideEffects 标志(纯的 ES2015 模块),安全地删除未用到的 export 导出
      • UglifyJsPlugin :删除未引用代码,并压缩
  • 移除 CommonsChunkPlugin插件,取而代之的是两个新的配置项(optimization.splitChunks 和 optimization.runtimeChunk)
  • module.loaders 替换为 modules.rules
  • webpack4 增加了 WebAssembly 的支持,可以直接 import/export wasm 模块,也可以通过编写 loaders 直接 import C++/C/Rust

splitChunks

可以参考https://segmentfault.com/a/1190000013476837

extra-text-webpack-plugin

关于[hash]和[chunkhash]的区别,简单来说,[hash]是编译(compilation)后的hash值,compilation对象代表某个版本的资源对应的编译进程。项目中任何一个文件改动,webpack就会重新创建compilation对象,然后计算新的compilation的hash值,所有的编译输出文件名都会使用相同的hash指纹,改一个就一起变。而[chunkhash]是根据具体模块文件的内容计算所得的hash值,某个文件的改动只会影响它本身的hash指纹,不会影响其他文件。

hash/chunkhash:

hash:在 webpack 一次构建中会产生一个 compilation 对象,该 hash 值是对 compilation 内所有的内容计算而来的

与整个项目的构建有关。只要整个项目中有文件更改,就会产生新的hash值,并且所有的文件共用一个hash值。

hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。但是在实际使用的时候,这几种hash计算还是有一定区别。

compiler对象代表的是配置完备的Webpack环境。 compiler对象只在Webpack启动时构建一次,由Webpack组合所有的配置项构建生成。

compilation对象代表某个版本的资源对应的编译进程。当使用Webpack的development中间件时,每次检测到项目文件有改动就会创建一个compilation,进而能够针对改动生产全新的编译文件。compilation对象包含当前模块资源、待编译文件、有改动的文件和监听依赖的所有信息。

compiler代表配置好的webpack环境,compilation针对随时可变得项目文件,只要文件有改动就会重新创建。

chunkhash:每一个 chunk 都根据自身的内容计算而来。

根据不同的entry进行文件依赖分析,构建对应的chunk,生成对应的hash值,即可以将一些公共库单独打包构建,只要不改动公共库的代码,生成的hash值就不变。

保证了在线上构建的时候只要文件内容没有更改就不会重复构建

contenthash:在js中引用css时,保证css不变的情况下hash值不变

juejin.cn/post/684490…

分析webpack中的Compiler/Compilation/Stats对象及构建顺序:github.com/liangklfang…

tsconfig.json

概述

如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录。tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。 一个项目可以通过以下方式之一来编译:

使用tsconfig.json

  • 不带任何输入文件的情况下调用tsc,编译器会从当前目录开始去查找tsconfig.json文件,逐级向上搜索父目录。
  • 不带任何输入文件的情况下调用tsc,且使用命令行参数--project(或-p)指定一个包含tsconfig.json文件的目录。

当命令行上指定了输入文件时,tsconfig.json文件会被忽略

配置示例

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "files": [
    "app.ts",
    "foo.ts",
  ],
  // 或者使用include,exclude指定待编译文件
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
  ]
}

compilerOptions 用来配置编译选项,files 用来指定待编译文件。

这里的待编译文件是指入口文件,任何被入口文件依赖的文件

files 属性是一个数组,数组元素可以是相对文件路径和绝对文件路径。

include 和 exclude 属性也是一个数组,但数组元素是类似 glob 的文件模式。它支持的 glob 通配符包括:

  • * :匹配 0 或多个字符(注意:不含路径分隔符)
  • ? :匹配任意单个字符(注意:不含路径分隔符)
  • **/ :递归匹配任何子路径

TS 文件指拓展名为 .ts、.tsx 或 .d.ts 的文件。如果开启了 allowJs 选项,那 .js 和 .jsx 文件也属于 TS 文件。

如果仅仅包含一个 * 或者 .* ,那么只有TS 文件才会被包含。

如果 files 和 include 都未设置,那么除了 exclude 排除的文件,编译器会默认包含路径下的所有 TS 文件。

如果同时设置 files 和 include ,那么编译器会把两者指定的文件都引入。

如果未设置 exclude ,那其默认值为 node_modules 、bower_components、jspm_packages 和编译选项 outDir 指定的路径。

exclude 只对 include 有效,对 files 无效。即 files 指定的文件如果同时被 exclude 排除,那么该文件仍然会被编译器引入。

前面提到,任何被 files 或 include 引入的文件的依赖会被自动引入。 反过来,如果 B.ts 被 A.ts 依赖,那么 B.ts 不能被 exclude 排除,除非 A.ts 也被排除了。

有一点要注意的是,编译器不会引入疑似为输出的文件。比如,如果引入的文件中包含 index.ts ,那么 index.d.ts 和 index.js 就会被排除。通常来说,只有拓展名不一样的文件命名法是不推荐的。

tsconfig.json 也可以为空文件,这种情况下会使用默认的编译选项来编译所有默认引入的文件。

官方编译选项列表:www.tslang.cn/docs/handbo…

可以参考:www.tslang.cn/docs/handbo…

tslint/eslint选择

ts.xcatliu.com/engineering…

Eslint规则:eslint.cn/docs/user-g…

参考掘金的eslint;juejin.cn/post/684490…

react配置eslint一些规则: segmentfault.com/a/119000001…

eslint 配置项
  • root 限定配置文件的使用范围
  • parser 指定eslint的解析器
  • parserOptions 设置解析器选项
  • extends 指定eslint规范
  • plugins 引用第三方的插件
  • env 指定代码运行的宿主环境
  • rules 启用额外的规则或覆盖默认的规则
  • globals 声明在代码中的自定义全局变量

git提交检测/husky/pre-commit/lint-staged

husky继承了Git下所有的钩子,在触发钩子的时候,husky可以阻止不合法的commit,push等等。

husky是一个npm包,安装后,可以很方便的在package.json配置git hook 脚本 。

比如,在 package.json 内配置如

 "scripts": {
    "lint": "eslint src"
  },
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint"
    }
  },

那么,在后续的每一次git commit 之前,都会执行一次对应的 hook 脚本npm run lint 。其他hook同理

pre-commit:github.com/PaicFE/blog…

lint-staged: github.com/okonet/lint…

prettire配置

prettier.io/docs/en/opt…

优化

  1. include & exclude
  2. babel-loader
  • cacheDirectory:默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。如果设置了一个空值 (loader: 'babel-loader?cacheDirectory') 或者 true (loader: babel-loader?cacheDirectory=true),loader 将使用默认的缓存目录 node_modules/.cache/babel-loader,如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。
  1. 引入babel runtime,避免重复引入
  2. resolve.modules解析时搜索的目录
  3. resolve.mainFields用于配置第三方模块使用那个入口文件

安装的第三方模块中都会有一个 package.json文件,用于描述这个模块的属性,其中有些字段用于描述入口文件在哪里,resolve.mainFields 用于配置采用哪个字段作为入口文件的描述。

可以存在多个字段描述入口文件的原因是因为有些模块可以同时用在多个环境中,针对不同的运行环境需要使用不同的代码。 以 isomorphic-fetch API 为例,它是 Promise的一个实现,但可同时用于浏览器和 Node.js 环境。

  1. resolve.alias配置项通过别名来把原导入路径映射成一个新的导入路径

  2. resolve.extensions在导入语句没带文件后缀时,webpack自动带上后缀去尝试查询文件是否存在

    1. 后缀列表尽可能小
    2. 频率最高的往前方
    3. 导出语句里尽可能带上后缀
  3. module.noParse让webpack忽略对部分没采用模块化的文件的递归解析处理

    1. 被忽略掉的文件里不应该包含 import 、 require 、 define 等模块化语句
  4. 使用动态链接库

    1. webapck.DllPlugin
      1. context (optional): manifest 文件中请求的上下文(context)(默认值为 webpack 的上下文(context))
      2. name: 暴露出的 DLL 的函数名 (TemplatePaths: [hash] & [name] )
      3. path: manifest json 文件的绝对路径 (输出文件)`
    2. webpack.DllReferencePlugin
      1. context: (绝对路径) manifest (或者是内容属性)中请求的上下文
      2. manifest: 包含 content 和 name 的对象,或者在编译时(compilation)的一个用于加载的 JSON manifest 绝对路径
      3. content (optional): 请求到模块 id 的映射 (默认值为 manifest.content)
      4. name (optional): dll 暴露的地方的名称 (默认值为 manifest.name) (可参考 externals)
      5. scope (optional): dll 中内容的前缀
      6. sourceType (optional): dll 是如何暴露的 (libraryTarget)
    3. libraryTarget 和 library
      1. output.libraryTarget 配置以何种方式导出库。
      2. output.library 配置导出库的名称。 它们通常搭配在一起使用。
    4. 推荐参考: segmentfault.com/a/119000001…
  5. HappyPack: 让Webpack把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。

    1. id: String 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件.

      loaders: Array 用法和 webpack Loader 配置中一样.

      threads: Number 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数。

      verbose: Boolean 是否允许 HappyPack 输出日志,默认是 true。

      threadPool: HappyThreadPool 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。

      verboseWhenProfiling: Boolean 开启webpack --profile ,仍然希望HappyPack产生输出。

      debug: Boolean 启用debug 用于故障排查。默认 false。

  6. ParallelUglifyPlugin:可以把对JS文件的串行压缩变为开启多个子进程并行执行&oq=可以把对JS文件的串行压缩变为开启多个子进程并行执行

  7. 在支持es6的环境中直接使用es6,不进行转换

  8. splitChunks: 提取公共代码

  9. 区分环境,使用mode

  10. 代码分离