写在最前
为何大多数人觉得搭建一个前端项目的开发环境很困难。首先,一个完整的开发环境需要依赖多个工具,每个工具又有不计其数的配置项,想要全部理解需要花费大量的时间。其次,一些官方文档中文翻译滞后,而且结构组织的也不是很好,没有从一个具体的项目出发讲解如何配置,只看API没有什么针对性。最后,好多国内相关博客要么没有标注工具的版本,导致按照步骤进行配置时会发现自己下载的和讲述的某些地方会不一致;要么只讲如何配置,不讲配置的原因,导致大家仍是云里雾里。不过所幸,大部分CLI实现了脚手架的功能,帮助快速生成项目,而不用了解工具的具体配置。
但是,作文里总会有个转折不是。当我们需要自己独立去创建一个项目的时候…该怎么办哖,Don’t be afraid,I’m here.
接下来会先介绍一下几个常用工具(Babel 7.9.0,Browserslist 4.11.1, ESLint 7.0.0, EditorConfig)的核心概念、安装和配置,最后会结合 TypeScript + SASS + webpack5 的项目来说如何将它们整合起来形成一个完整的前端项目开发环境。 Here we go.
Babel (7.9.0)
Babel是一个Javascript编译器。主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容(backwards compatible)的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其它环境(如:node)中。
安装
npm i -D @babel/core @babel/cli @babel/preset-env
npm i -D core-js
运行此命令之前要先初始化项目,新建项目文件夹 xxx , 在该文件夹下 运行:
npm init
尽量不要一路回车,建议按实际内容来。
@babel/core
主要用于将源代码(JS\TS)解析为AST(抽象语法树)。
@babel/cli
用于从命令行编译文件。
@babel/preset-env
预设环境。源代码解析成AST后,还需要进行转换和生成,这是由插件来做的。@babel/preset-env(预设环境)是常用预设和插件的集合(babel-preset-es2015,babel-preset-es2016,babel-preset-es2017,babel-preset-latest,babel-preset-node5, babel-preset-es2015-node等,@babel/preset-env不支持stage-x插件)。
最初每年EcmaScript标准更新,都需要使用者手动添加最新年份的预设才能进行新语法的转换:
"presets": [
"es2015",
"es2016",
"es2017"
],
后来改成了 babel-preset-latest,意思为最新的预设(包含了以往所有年份),不用每年都需要手动添加一把。最终 latest 也被废弃,变成了目前的 preset-env。开发人员可以在代码中直接书写已经正式发布的特性。不过,当ES更新时,肯定还需要更新一下 @babel/preset-env。
core-js
Babel 默认只转换新的 JavaScript 语法,如: 类、箭头函数、扩展运算(spread),而不转换新的 API ,如:Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(如:Object.assign)。不转码的 API 详细清单可以查看 definitions.js 文件。
core-js 提供了es5、es6+的polyfills(填充物,用于实现浏览器并不支持的原生API的代码,它将一个新的 API 引入到一个旧的环境中,仅靠旧环境中已有的技术手段实现),包括promises, symbols, collections, iterators, typed arrays等等。
这里不得不提一下该库和它的俄罗斯作者。该库一周的下载量比大家熟知的 Angular、 React、Vue加一起的下载量还要多的多。就这样的一个库谁能想到它的作者竟然需要在命令行里 looking for a good job,后来该作者又因为骑摩托车撞死一人,伤一人,被判入狱18个月, 现在应该已经处于服刑期间了…
@babel/polyfill 已废弃。
配置
可以在三类文件中对Babel进行配置,在项目的根目录中创建babel.config.*、.babelrc.* (* 可为 空,json, js, cjs 或 mjs) ,或在package.json中添加。
如果扩展名为 json,那一般的内容是:
{
"presets": [...],
"plugins": [...]
}
如果扩展名为 js,则内容为:
const presets = [ ... ];
const plugins = [ ... ];
module.exports = { presets, plugins };
在 package.json 中:
{
"name": "my-package",
"version": "1.0.0",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
我们一般使用 babel.config.js 作为配置文件,因为在JS中可以写注释,方便理解(JSON不方便)。其它工具的配置文件能采用 js 做扩展名的也尽量使用 .js。
在项目文件夹下创建 src 目录,新建 index.js。编写内容:
const userInfo = new Map();
userInfo.set('name', '天下布武');
userInfo.set('age', 18);
console.log(Object.entries(userInfo));
在package.json中添加:
"scripts": {
"build": "./node_modules/.bin/babel src/index.js --out-dir dist"
},
@babel/preset-env
最简设置如下(不建议,转换后会多出很多冗余代码,至少要配置一个targets):
module.exports = {
presets: [
[
'@babel/preset-env',
],
],
};
在命令行中运行:
npm run build
就能在新生成的dist文件夹中看到 编译后的 index.js :
{% asset_img noconst.jpg %}
可以看到之前的 const 被转译成了 var。
下面对两个常用参数进行说明:
targets
设置编译代码的目标平台,可以是浏览器也可以是node环境。如不设置,会将所有ES6+的代码编译为ES5-。建议设置,这样可以按目标平台来决定是否进行转换,以避免增加不必要的补丁,减少打包后的代码体积。
常见设置如下:
module.exports = {
presets: [
[
'@babel/preset-env', {
targets: '> 1%, last 2 versions, ie > 8',
},
],
],
};
以上配置的意思是要兼容全球范围内使用量大于1%的浏览器和它们最新的两个版本,加上IE 9-11。这里既可以指定浏览器的版本,也可以通过查询的方式来确定要支持的浏览器。详情见Browserslist这一章节。
若设置 targets: ‘> 10%’ , 再运行一次 npm run build ,因为大于 10% 的浏览器只有谷歌,它是支持 const 的, 因此编译结果几乎原封未动:
{% asset_img const.jpg %}
建议使用 .browserslistrc 文件来定义targets,这样其它工具(Autoprefixer、PostCSS、Stylelint 等)也能通过该配置获取到目标浏览器,从而做有针对性的处理。
useBuiltIns
是否内置兼容。如果设置,就可以不用在HTML中引入polyfill的JS文件来处理浏览器的兼容性问题了。常见设置如下:
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
},
],
],
};
可选值包括:“usage” | “entry” | false。
-
false:默认值,即不引入polyfills,不做浏览器兼容。
-
entry:根据配置的浏览器,引入这些浏览器不兼容的polyfill。这个是在文件中已经明确写了import “core-js” 或其中具体某个模块(如:import “core-js/es/array”)的情况下,babel会根据 browserslist 自动添加指定浏览器不兼容的该模块下的所有polyfill,无论当前代码是否需要。
-
usage:会根据配置的浏览器以及代码中用到的 API 自动添加polyfill,实现了按需引入。一般使用这个配置。
只有当 useBuiltIns 的值为 entry 或 usage 时,“corejs” 这个选项才会起作用。这里如无特殊情况尽量指定 core-js 的版本为 3(默认为 2), 因为它有很多2没有的新特性: “corejs”: 3。
这里运行 npm run build, 运行结果为:
{% asset_img builtin.jpg %}
可以看出它在全局作用域下添加了 Map 变量,同时在 Object 原型中添加了 entries 方法。
babel默认转出的是模块规范为 commonJS,只能在 Node 环境中使用,如果想在浏览器环境中使用,一般有两种方式:
1、 结合 rollup
2、 结合 webpack
我们一般使用 第二种方式,参看最后 webpack 实例。
@babel/plugin-transform-runtime
对于一般应用开发来说,直接使用上述的 polyfill 方案是比较方便的,但如果是开发工具、库的话,这种方案未必适合(由上图可以看出,polyfill 是添加自定义全局对象 或向对象的 prototype 上添加方法实现的)。使用 @babel/plugin-transform-runtime 这个插件就可以解决这个问题。
安装:
npm i -D @babel/plugin-transform-runtime @babel/runtime-corejs3
配置:
module.exports = {
presets: [
[
'@babel/preset-env',
],
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
},
],
],
};
**该插件无法读取 preset-env 的 targets 或者是 browserlist 中的目标平台参数,因此不会根据目标平台来决定是否进行转换和注入 polyfills,而是全转换和注入所有。以后可能会在 useBuiltIns 中增加一个参数 “runtime” 来代替该插件。**可参见:https://github.com/babel/babel/issues/10133
再运行 npm run build,得到:
{% asset_img runtime.jpg %}
凡是require进来的模块都直接赋值给普通变量,不会对 Map 、 Object 等全局变量造成影响。
注:这里的和上边所有提到的“编译”一词更准确的说法应该是“转译(Transpile)”。
Browserslist (4.11.1)
Browserslist 是一个能够在不同的前端工具间共享目标浏览器的配置。看配置就知道当前项目支持的浏览器有哪些。它使用 Can I Use 的数据做查询。
在项目中添加 Browserslist,常用有两种方式(不能同时在 .browserslistrc 和 package.json 中配置,否则使用 Babel 转译的时候会报错):
- 在项目的根目录下添加 .browserslistrc 文件
> 1%
last 2 versions
ie > 8
- 在package.json文件中增加 browserslist 节点
{
"private": true,
"dependencies": {
},
"browserslist": [
"last 2 version",
"> 1%",
"ie > 8"
]
}
查询结果可通过 https://browserl.ist/ 来查看。
运行命令行:
npx browserslist
可查看当前项目目标浏览器列表。如果只是在 babel.config. 文件中配置了 target 是检测不出来的(会优先使用),因此建议使用 单独的 .browserslistrc 来设置。*
ESLint (7.0.0)
是一个可以查找并且修复JavaScript(TypeScript)中错误的工具,目的是为了保证代码风格统一,避免出错。
概念
extends(扩展)
扩展里填的内容是包含了一系列规则的配置文件。这个一般不需要自己定义,因为有很多现成的:如ESLint自身的 eslint:recommended、eslint:all 和社区的 google、airbnb。
配置的模块名(npm的包名)要为 eslint-config-xxx,在配置中可缩写为 xxx。
例:
{% asset_img google.jpg %}
plugins(插件)
extends 中是对 eslint 现有规则的一系列预设(开启或关闭),而 plugins 不仅可以定义预设,也可以自定义规则(比如自己创建一个 no-console2,区别于ESLint的 no-console),甚至可对不是JavaScript类型的文件(如 *ts,*.md,*.vue)扩展自己的规则。
插件的模块名一般为 eslint-plugin-xxx,在配置中可缩写为 xxx。
例:
{% asset_img react.jpg %}
rules(规则)
直接配置已有规则的开启、关闭。比如强制在JavaScript中使用单引号(“quotes”: [2, “single”])。规则定义中参数的设置说明:
-
“off” 或 0:关闭规则
-
“warn” 或 1:警告,不会影响程序执行
-
“error” 或 2:错误,程序不能执行
安装
针对 JavaScript
npm i -D eslint eslint-config-airbnb-base eslint-plugin-import
eslint-config-airbnb-base
包含了airbnb 最基础(不包含 React 相关)的JS编码风格规则。
eslint-plugin-import
上边的插件依赖这个。😛
针对 TypeScript
npm i -D eslint eslint-config-airbnb-typescript eslint-plugin-import @typescript-eslint/eslint-plugin
eslint-config-airbnb-typescript
Airbnb 风格的 TypeScript 支持。它将一些常见配置都加了进去,省下了好多工作量。
ps. 这是一个匈牙利布达佩斯技术和经济大学的学生做的,想想自己的大学生活都在做啥…
该插件包含了@typescript-eslint/parser(TypeScript 解析器),它调用@typescript-eslint/typescript-estree(通过在给定的源代码上调用 TypeScript 编译器,就是 npm i typescript -D 安装的那个,以产生TypeScript AST,然后将该AST转换为ESLint期望的格式)。ESlint默认的解析器叫 espree。
@typescript-eslint/eslint-plugin
与 @typescript-eslint/parser 结合使用时,运行 TypeScript 的分析规则。
配置
可使用 .eslintrc.( 可为 空,js, yaml, yml, json)或在 package.json 中配置,这里使用 .eslintrc.js 来进行配置。 一个项目中在不同的文件夹下可以有多个 .eslintrc. 配置文件,这样可以约束不同文件夹下的文件使用不同的风格,这一点和 editorConfig 一样。
针对 JavaScript
一般配置如下:
module.exports = {
root: true, // 意思是到头啦,不要再向上找了
env: { // 代码将会在哪些环境中运行。每个环境都附带了一组特定的预定义全局变量,如 browser 中有 window,document等,添加后可以直接在代码中使用,而不报错。
browser: true,
node: true,
es2020: true,
},
extends: 'airbnb-base', // 使用airbnb风格
parserOptions: {
ecmaVersion: 2020, // 启用ES2020的语法
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 只在打包时强制不使用console
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 只在打包时强制不使用debugger
},
};
针对 TypeScript
一般设置如下:
module.exports = {
root: true, // 意思是到头啦,不要再向上找了
env: { // 代码将会在哪些环境中运行。每个环境都附带了一组特定的预定义全局变量,如 browser 中有 window,document等,添加后可以直接在代码中使用,而不报错。
browser: true,
node: true,
},
extends: [
'airbnb-typescript/base', // 使用airbnb风格
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parserOptions: {
project: './tsconfig.json', // 要在tsconfig中设置编译的版本
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 只在打包时强制不使用console
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 只在打包时强制不使用debugger
},
};
最后要在 VS Code 中安装 ESLint 插件,配置相关参数,使之能够在文件保存时自动修复格式错误;WebStrom中则需设置允许 ESLint(最新的2020.1.1 版本中也能够在文件保存时自动修复错误)。
EditorConfig
帮助在不同的编辑器或IDE上从事同一项目的多个开发人员保持一致的编码样式。
一个项目里可以有多个.editorconfig 分别放置在不同的文件夹中,当 VS Code 这类编辑器打开一个文件时,它会检查这个文件所在目录和它的父级文件夹(直到项目根目录或者是是某个文件夹中的 .editorconfig 里标识了 root = true 才会停止)中是否存在 .editorconfig。被打开的文件格式会以距当前文件最近的 .editorconfig 中的内容为准。
一般配置如下:
# 告诉编辑器这是最顶层的(不要再向上找了) EditorConfig 文件
root = true
[*]
charset = utf-8 # 设置编码为utf-8
indent_style = space # 缩进方式为空格
indent_size = 2 # 缩进大小为2个字符
end_of_line = lf # 换行符,可选"lf"、"cr"、"crlf"
trim_trailing_whitespace = true # 删除行尾空格
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
webpack (v5.0.0-beta.16)
webpack是用于现代 JavaScript 应用程序的静态模块打包器。
当webpack处理应用程序时,它会在内部构建一个依赖关系图,该图映射项目所需的每个模块最终会生成一个或多个包。
概念
modules
webpack中,无论是 JS 、CSS 还是图片等,总之一切皆模块。 有点像RxJS,一切皆数据流。
模块间依赖的表述有很多种方式,如:import,require,define,@import,url(…), <img src=…> 等等。
Entry & Output
入口指示webpack应该使用哪个模块开始构建其内部依赖关系图。默认为: ./src/index.js。
出口告诉webpack在何处发出它创建的包文件以及如何命名这些文件。默认为: ./dist/main.js
Loaders
webpack 默认只能解析JavaScript和JSON,可以通过添加 Loaders 来处理其他类型的文件。
Plugins
可以利用插件来执行更广泛的任务,例如打包优化,资产管理和环境变量的注入。
Mode
分development、production、none三种,每种都会对应一系列默认配置。
实战
接下来以初始化一个 TypeScript + SCSS 项目为例,介绍下webpack5的配置流程。
在开始之前先说下,为何没用 ts-loader 和 TSLint:
-
由于 TSLint 的性能不如 ESLint,再加上有很多热门的社区(React Hooks、Vue),都是通过 ESLint 来构建规则,因此,TypeScript 团队决定专注支持 ESLint。
-
Babel7 虽然不支持TS类型检查,但已经支持转译。 构建需要安装的插件、工具太多,能少一个就少一个,一个编译器既能支持JS,又能支持TS,为何不用。因此,感觉 ts-loader 的生命也不会太长了…
Node 环境要求至少为:10.13.0。
初始化项目
npm init
在命令行中填入项目相关信息,不建议一路回车…
安装 webpack
webpack@next 是最新的 webpack5。不写@next,就是 webpack4。
npm i -D webpack@next webpack-cli
如果需要一个web服务器做调试和热更新,则需安装:
npm i -D webpack-dev-server
创建 .editorconfig
统一编码样式。
# 告诉编辑器这是最顶层的(不要再向上找了) EditorConfig 文件
root = true
[*]
charset = utf-8 # 设置编码为utf-8
indent_style = space # 缩进方式为空格
indent_size = 2 # 缩进大小为2个字符
end_of_line = lf # 换行符,可选"lf"、"cr"、"crlf"
trim_trailing_whitespace = true # 删除行尾空格
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
安装 TypeScript 编译器并配置
安装编译器的目的是为了配合ESLint做代码检查和自动修复。
npm i -D typescript
在项目根目录下创建 tsconfig.json,其中可定义入口文件以及编译的参数,用于将 TypeScript 转译为 JavaScript。
一定要设置,否则在IDE做语法校验的时候,新JavaScript API(如: Object.fromEntries )会报错。
{
"compilerOptions": {
"lib": [
"ESNext",
"DOM"
]
}
}
ESNext 指的是TypeScript支持的最新版本的ES。它会随着ES版本的更新而自动更新,一劳永逸。
DOM 类型定义,允许在TS中直接写window,document。
安装 ESLint 并配置
npm i -D eslint eslint-config-airbnb-typescript eslint-plugin-import @typescript-eslint/eslint-plugin
.eslintrc.js
module.exports = {
root: true, // 这个配置同 editorConfig,意思是不要找了
env: { // 代码将会在哪些环境中运行。每个环境都附带了一组特定的预定义全局变量,如 browser 中有 window,document等,添加后可以直接在代码中使用,而不报错。
browser: true,
node: true,
},
extends: [
'airbnb-typescript/base', // 使用airbnb风格
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parserOptions: {
project: './tsconfig.json', // 要在tsconfig中设置编译的版本
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 只在打包时强制不使用console
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 只在打包时强制不使用debugger
},
};
创建 .gitignore
提交Git服务器时,忽略的文件列表。使用SVN的就不需要这个了。
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.git
创建 .browserslistrc
这里设置兼容IE11。**当前项目如果是作为库使用的话,该设置不会起作用。**详情见:babel-plugin-transform-runtime
> 1%
last 2 versions
not ie < 11
安装 webpack loaders
所有 Loaders 都在 webpack.config.js 文件中的 module 节点中进行添加。
转译
babel-loader及相关
webpack本身只能打包(模块合并)而没有转译的能力,TS转译成JS用的是 Babel ,并没有使用 TypeScript 编译器。
npm i -D babel-loader
npm i -D @babel/core @babel/preset-env @babel/preset-typescript core-js
babel.config.js:
module.exports = {
presets: [ [
"@babel/preset-env",{
"useBuiltIns": "usage",
"corejs": 3
}],
"@babel/preset-typescript"
]
}
webpack.config.js 片段:
module: {
rules: [
// 使用 babel-loader 解析 ts, js, tsx, jsx 文件.
{
test: /\.(ts|js)x?$/, // 匹配扩展名为 .ts,.js,.tsx.jsx 的文件
use: 'babel-loader', // 使用 babel-loader 来进行解析
exclude: /node_modules/, // 排除 node_modules 文件下的文件
},
]
}
更多配置可见下方完整的配置。
当前项目将要作为库的话,还得安装:
npm i -D @babel/plugin-transform-runtime @babel/runtime-corejs3
同时 babel.config.js 中的配置改成下面这个:
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-typescript',
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
corejs: 3,
},
],
],
};
样式
sass-loader
加载 SASS/SCSS 文件 并将其编译为 CSS。
npm i -D sass-loader node-sass
sass-loader 要求要安装 Dart Sass 或者是 Node Sass。弄过 NPM 下载下来的这两者只是个编译器。据说前者在node环境中性能比后者要差,因此一般都会使用 node-sass。
webpack.config.js 一般配置如下:
// 执行顺序:从右到左
{
test: /\.s[ac]ss$/i,
use: [
// 从字符串中创建style标签到HTML的Header中
'style-loader',
// 转换CSS为字符串
'css-loader',
// 给CSS添加前缀以适应各浏览器
'postcss-loader',
// 编译 Sass 到 CSS
'sass-loader',
],
},
postcss-loader
PostCSS是CSS语法转换的工具。它提供API来对CSS文件进行分析和修改它的规则。
利用其插件 autoprefixer ,可以给 CSS 添加目标浏览器(Browserslist中定义的)前缀。
-moz- /* 火狐等使用Mozilla浏览器引擎的浏览器 */
-webkit- /* Safari, 谷歌浏览器等使用Webkit引擎的浏览器 */
-o- /* Opera浏览器(早期) */
-ms- /* Internet Explorer */
npm i -D postcss-loader autoprefixer
创建 postcss.config.js, 定义 PostCSS 的插件为 autoprefixer:
module.exports = {
plugins: {
autoprefixer: {},
},
};
css-loader
加载CSS文件,并以JS模块(CSS样式以字符串的形式封装在其中)的形式返回。
- 如果只使用 css-loader,解析出的CSS内容都在打包后的js代码中,没有任何作用。
- 只有配合 style-loader 或 mini-css-extract-plugin 等,引用的样式才会起作用。
npm i -D css-loader
style-loader
将CSS样式注入到DOM中。默认是在<header>最后添加<style>,这是通过生成JS方法动态添加的。
npm i -D style-loader
一般这样配置:
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
文件
url-loader
将小于一个限制大小的文件转换为base64 URIs。超过限制的,会默认使用 file-loader来做处理。所以这里一定要把 file-loader 也安装上。
npm i -D url-loader file-loader
file-loader
生成文件到输出的文件夹中,并返回相对路径URL 。
npm i -D file-loader
安装 webpack plugins
HTMLWebpackPlugin
会生成一个HTML5文件,其中body中会加入所有webpack打包出来的内容。
npm i -D html-webpack-plugin
clean-webpack-plugin
移除或清空构建出的文件夹。
npm i -D clean-webpack-plugin
mini-css-extract-plugin
将CSS提取到单独的CSS文件中。
npm i -D mini-css-extract-plugin
一般的配置如下:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module: {
rules: [
...
{
test: /\.s[ac]ss$/i,
use: [
// 提取到单独的CSS文件
MiniCssExtractPlugin.loader,
// 转换 CSS 到 CommonJS
'css-loader',
// 给 CSS 添加前缀以适应各浏览器
'postcss-loader',
// 编译 Sass 到 CSS
'sass-loader',
],
},
],
},
plugins: [
...js
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: 'css/[id].css',
}),
],
webpack-bundle-analyzer
可视化webpack输出文件的大小。
npm i -D webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
创建 webpack.config.js
配置webPack来处理TypeScript。
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const config = {
entry: './src/index.ts', // 入口文件
externals: {
cesium: 'Cesium',
},
module: {
rules: [
// 使用 babel-loader 解析 ts, js, tsx, jsx 文件.
{
test: /\.(ts|js)x?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
// 执行顺序:从右到左
{
test: /\.s[ac]ss$/i,
use: [
// 提取到单独的CSS文件
MiniCssExtractPlugin.loader,
// 转换 CSS 到 CommonJS
'css-loader',
// 给 CSS 添加前缀以适应各浏览器
'postcss-loader',
// 编译 Sass 到 CSS
'sass-loader',
],
},
// 使用 url-loader 将小于 4KB 图片 转换为 base64 URIs
{
test: /\.(png|jpe?g|gif|webp)$/i,
use: [
{
loader: 'url-loader',
options: {
name: 'images/[name].[hash:8].[ext]',
limit: 4096,
esModule: false,
},
},
],
},
],
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], // 按顺序解析以上扩展名的文件(必须添加,否则通过import进来的文件无法解析。import时可不写扩展名 默认值为 ['.wasm', '.mjs', '.js', '.json']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({ template: 'public/index.html', title: 'TypeScript-Webpack-starter' }),
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: 'css/[id].css',
}),
],
};
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.devtool = 'source-map'; // 导出SourceMap供调试
}
if (argv.mode === 'production') {
}
return config;
};
某些开源项目会在项目的根目录下创建一个 build 文件夹,将 webpack 的配置拆分为base(存放公用配置)、dev(开发环境特有的配置)、prod(生产环境特有的配置),利用合并插件将dev或prod和base合并。个人认为其拆分的思想没有问题,但如果配置项没达到一定规模(500行以上?)可以不用这么麻烦。如上方配置所示:只使用 webpack.config.js ,先添加公用配置(一个对象),然后通过判断当前是开发还是生产模式,补充相应配置。
添加脚本
package.json
"scripts": {
"serve": "webpack-dev-server --mode development --open",
"dev": "webpack-dev-server --mode development --open",
"build": "webpack --mode production"
},
形成项目结构
创建libs、public、src文件夹
├── libs // 第三方库
├── public
│ ├── index.html
├── src
│ ├── assets // 资源
│ │ ├── images // 图片
│ │ ├── styles // 样式文件 scss、less、css
│ └── index.ts
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── babel.config.js
├── package.json
├── postcss.config.js
├── README.md
├── tsconfig.json
└── webpack.config.js
其中index.html
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<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><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
源码地址
https://github.com/THS-FE/typescript-webpack-starter
备注
为什么好多配置文件的后缀是 rc
[Unix: from runcom files on the CTSS system 1962-63, via the startup script /etc/rc] Script file containing startup instructions for an application program (or an entire operating system), usually a text file containing commands of the sort that might have been invoked manually once the system was running but are to be executed automatically each time the system starts up.
rc代表短语 runcom (运行命令),unix的爷爷CTSS系统中的脚本文件,里边包含了应用或者整个系统启动时要执行的命令。
现在更通用的含义可能是 runtime configration,即应用运行时的配置。
参考:https://stackoverflow.com/questions/11030552/what-does-rc-mean-in-dot-files
https://unix.stackexchange.com/questions/3467/what-does-rc-in-bashrc-stand-for