在上一篇我们讲了Typescript + React + Webpack4的基本配置,通过在上一篇的配置,我们能够:
1:通过npm run dev在浏览器里面看到一个只包含html的页面
2:通过npm run build打包可用于上线的文件
但是,在一个完整的前端项目里,以上的配置满足了大体的框架,但是还有很多细节需要补充
1:SCSS相关配置
1.1:安装node-sass, sass-loader, css-loader, style-loader
npm install --save-dev node-sass sass-loader css-loader style-loader
node-sass - 把SCSS编译为CSS(被sass-loader使用)
sass-loader - 把SCSS编译为CSS
css-loader- 使得@import 和 url() 可以工作
style-loader- 通过使用<style>把css添加到HTML文件的<head>里面,从而使css生效
1.2: SCSS的webpack相关配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devtool: "source-map",
resolve: {
extensions: ['.ts', '.tsx', '.js', '.scss']
},
entry: {
main: './src/index.tsx'
},
output: {
path: path.join(__dirname, '/dist'),
filename: "bundle.min.js"
},
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_moduls/,
use: [
{
loader: "awesome-typescript-loader"
}
]
},
{
enforce: "pre",
test: /\.js$/,
loader: 'source-map-loader'
},
{
test: /\.scss/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
})
]
}
要使得SCSS能正确工作,我们修改了2个地方,
1 : 一个是在resolve:{extensions: [ ]}里面添加了'.scss'
2: 在module:rules:[ ]里面添加了跟css相关的loader:
{
test: /\.scss/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
注意:
1:这里我们没有添加node-sass,因为实际上是sass-loader来负责把SCSS编译为CSS,而node-sass又是sass-loader的依赖,所以我们需要安装它。
2:['style-loader', 'css-loader', 'sass-loader']这三个loader的顺序是重要的,必须按照上面的代码示例里面的那样,不然会收到编译错误。
2:Image相关配置
2.1 安装file-loader
npm install --save-dev file-laoder
file-laoder用来识别使用了import/require()的文件,把之转化为一个url,并且打包之后放到output目录下
2.2 配置file-loader
在module:rules:[ ]里面添加跟file-loader相关的配置:
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
2.3 添加图片文件,编写使用图片代码
首先看一下我们src下面的结构:
我们可以看到当我们使用import 是引入一个图片的时候,报错了,如果鼠标hover上去或者重新build以下,我们会得到一个错误:
TS2307: Cannot find module './images/moon.jpg'.
这里报的错是TS2307,是Typescript的一个错。这个错误的原因是当使用import去引入一张图片的时候,Typescript并不能识别图片文件。
怎么办呢?解决办法有2个:
1:当引入图片文件的时候,使用require而不使用import
2:为图片文件编写.d.ts文件
但是,在Typescript环境下使用require,需要先安装@types/requirejs,不然之后TS又会抱怨找不到require
npm install --save-dev @types/requirejs
2: 修改代码
把之前的
import Moon from './images/moon.jpg'
改为:
const Moon = require('./images/moon.jpg');
这个时候,我们能在页面上看到我们的图片了。但是!,我们依然会得到TS的error:
ERROR in [at-loader] ./node_modules/@types/requirejs/index.d.ts:422:13
TS2403: Subsequent variable declarations must have the same type. Variable 'require' must be of type 'NodeRequire', but here has type 'Require'.
我们可以看到这个error是来自于我们刚刚安装的./node_modules/@types/requirejs。对于我们安装的第三方库,我们无法去修改它的代码。
所以,一个最简单的解决这个问题的办法就是不要使用@types/requirejs!而安装@types/webpack-env, 并且把我们之前安装的@types/requirejs给去掉。安装了@types/webpack-env,你依然可以使用require,并且@types/webpack-env本身不会报错:
npm install --save-dev @types/webpack-env
npm uninstall @types/requirejs
好的,现在你不会得到任何错误,并且愉快地使用require( )来加载你的图片了。
3:打包优化
3.1 使用code splitting
wepack.config.json
output: {
path: path.join(__dirname, '/dist'),
filename: "[name].[contenthash].js"
}
当我们使用了contenthash,打包出来的文件会根据代码是否变化了而在文件名字里面出现不同的hash值。这样的好处就是,强迫浏览器去服务器下载最新的文件,而不让用户还使用老文件。
我们在命令行里面使用了以下命令后会得到下面的文件:
npm run build
大家可以从截图上看到,打包之后的文件里面有一个js文件:main.27c4cc56e2d346938472.js,它的大小是132 KiB。
但是,以上的打包方式存在一个问题。我们的代码是由两部分组成:我们自己写的业务代码 + 依赖的第三方库(例如react,react-dom等)。我们的业务代码是经常变化的,所以当我们的业务代码有变化时,我们需要重新打包,从而得到一个不同hash值的main.xxx.js文件,假如它是200KB,那么用户得重新下载200KB的代码。但是,这200KB里面,其实包含了不会变化的第三方依赖库代码(假如是50KB),这部分如果可以被浏览器缓存起来,那么用户便不会再需要重新下载200KB,而只是业务那部分的150KB。
那么假如,我们有办法把这部分变化的(业务代码)和不变的(第三方依赖)打包成2个文件,那就可以解决以上问题了。事实上,webpack 4里面,你可以通过splitChunks来实现:
webpack.config.json
module.exports = {
// 其他的配置项省略了
optimization: {
splitChunks: {
chunks: "all"
}
}
}
添加上面的配置项之后,我们再来跑npm run build,得到以下结果:
对比之前,我们可以看到现在我们多了一个vendors~main.9c44b1b7bc198b90e63f.js文件,而现在的main.94e269bacef895deeefb.js文件大小就只有2.37 KiB了。
3.2 把css从js文件里面抽离出来
大家可能注意到了,我们之前明明添加了一个App.scss文件,但是我们打包出来的文件却没有css文件。因为我们把css代码也都打包到main.xxx.js文件里面去了。但是,更好的做法是把css和js分来。为了实现这个目的,我们需要用到:mini-css-extract-plugin。首先,安装:
npm install --save-dev mini-css-extract-plugin
现在我们已经安装好了,在进行我们的config之前,有必要先了解一下mini-css-extract-plugin的用法。mini-css-extract-plugin应该只能被用在production环境,且此时不应该使用style-loader。
所以,这里我们有个需求,我们需要在webpack.config.js文件里面拿到当前的mode,从而知道是在development环境还是production环境。为了达到这个目的,我们的webpack.config.js需要export一个function而不是object,从而在function里面通过argv拿到mode。所以我们的webpack.config.js变成了:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = (env, argv)=>{
const isDevMode = argv.mode === 'development';
return {
devtool: "source-map",
resolve: {
extensions: ['.ts', '.tsx', '.js', '.scss']
},
entry: {
main: './src/index.tsx'
},
output: {
path: path.join(__dirname, '/dist'),
filename: isDevMode ? "[name].[hash].js" : "[name].[contenthash].js"
},
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_moduls/,
use: [
{
loader: "awesome-typescript-loader"
}
]
},
{
enforce: "pre",
test: /\.js$/,
loader: 'source-map-loader'
},
{
test: /\.scss/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'sass-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader',
],
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: isDevMode ? '[name].css' : '[name].[contenthash].css',
chunkFilename: isDevMode ? '[id].css' : '[id].[contenthash].css'
})
],
optimization: {
splitChunks: {
chunks: "all"
}
}
}
}