Index
环境要求
这次的配置方案不会涉及到CRA。
请务必安装Node.js的最新版本,可以参照本文安装。
自定义证书部分会用到mkcert,mac下可在控制台中输入 brew install mkcert 来安装,windows下则使用choco install mkcert 。brew或chocolatey的安装方式请自行搜索。
本文默认使用chrome进行调试。
开始吧
建立项目文件夹
让我们新建一个文件夹,命名为react-demo,用vscode打开该文件夹后,选择菜单中的终端->New Terminal 来新建终端窗口。在终端中输入:
npm init -y
上面命令会在你的根目录生成 package.json 文件,该文件定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。
安装webpack
因为我们使用的是 webpack 4+ 版本,还需要安装 webpack-cli ,在终端中输入:
npm install --save-dev webpack@4 webpack-cli@3
执行完成后,你会发现在 package.json 文件中多了 devDependencies 属性,这是因为命令中使用了 --save-dev的结果,代表了开发依赖。和实际运行无关的开发依赖我们都应该加入devDependencies。
接下来,我们在项目根目录下新建config文件夹,在该文件夹会下保存webpack的配置文件。在config目录下新建一个 webpack.common.config.js 文件,并输入:
const path = require('path');module.exports = { entry: { app: './src/app.js', }, output: { filename: 'js/bundle.js', path: path.resolve(__dirname, '../dist') }}
entry 属性定义了入口文件,output 定义了编译后的文件名及所在目录。上述代码告诉webpack,把根目录src文件夹下的app.js打包到根目录dist下的js目录中,更名为bundle.js。
为此,我们需要在根目录下建立一个src文件夹,在src下新建一个app.js。然后在 package.json 中配置如下属性:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack --config ./config/webpack.common.config.js" },
让我们在终端执行:
npm run start
你会发现根目录多出了一个文件夹: dist/js ,其中有一个js文件: bundle.js ,那么至此,我们已经成功编译打包了一个js文件,即入口文件: app.js 。
拆分webpack
同一个项目,在不同的运行环境下会有不同的要求。比如在生产环境中,我们需要压缩生成的js文件,而在开发环境中我们并不想压缩js,因为这不太方便我们调试。
在config中添加2个新的js文件,webpack.prod.config.js 和webpack.dev.config.js,这2个文件分别对应生产环境和开发环境。再利用webpack-merge将公用配置分离在 webpack.common.config.js 中。
在终端中输入:
npm install --save-dev webpack-merge
安装结束后,在 webpack.prod.config.js 中输入以下代码:
const { merge } = require('webpack-merge'); const common = require('./webpack.common.config.js'); module.exports = merge(common, { mode: 'production', });
回到 package.json,修改如下:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config ./config/webpack.prod.config.js" },
好了,接下来编译打包看看效果吧,在终端执行:
npm run build
你会看到和之前的start指令一样生成了新的js。
安装React
在终端中输入:
npm install --save react react-dom
安装完成后我们就能写react的JSX了。
我们在src下新建 index.js 并输入以下代码:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './app.js'; function onListShow(ev) { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, kintone.app.getHeaderSpaceElement()); return ev; } kintone.events.on("app.record.index.show", onListShow);
在app.js中输入如下代码:
import React from 'react'; function App() { return ( <div className="App"> <h1>Hello World</h1> </div> ); } export default App;
对 webpack.common.config.js 的入口文件进行修改 :
entry: { index: './src/index.js', },
使用babel 7
webpack并不支持jsx语法,我们需要利用babel 7做编译转换。
首先安装babel相关的模块,在终端输入:
npm install --save-dev babel-loader @babel/preset-react @babel/preset-env @babel/core
在根目录下新建 babel.config.json 来配置相关的presets,输入下面代码:
{ "presets": [ [ "@babel/preset-env", { "targets": { "edge": 17, "firefox": 60, "chrome": 67, "safari": 11.1 }, "useBuiltIns": "usage" } ], "@babel/preset-react" ]}
再修改 webpack.common.config.js :
module.exports = { //................., module: { rules: [ { test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/, } ] }}
上述代码告诉webpack,所有以js/jsx为后缀的文件都需要通过babel-loader进行转换,但不需要去处理node_modules下的文件。
接下来让我们运行
npm run build
dist目录下生成了bundle.js,把它上传到kintone上的某个app中看看效果吧。
优化生产环境
换个不确定的文件名
kintone并不能很直观的显示当前自定义的js的版本,最简单的方法就是给予不同的命名。利用 [hash] 或[chunkhash] 即可。
修改webpack.prod.config.js的exports代码
module.exports = merge(common, { mode: 'production', output: { filename: 'js/[name].[chunkhash:8].bundle.js', },
output的filename直接覆盖了webpack.common.config.js下的配置,让我们再运行一下
npm run build
看看输出的文件名把。
清理dist文件夹
生成了新的js后,我们发现,旧的bundle.js没有被移除。利用clean-webpack-plugin来帮我们做预处理。
在终端输入:
npm install --save-dev clean-webpack-plugin
修改 webpck.prod.config.js :
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = merge(common, { //.........., plugins: [ new CleanWebpackPlugin() ], });
再度运行
npm run build
看看效果把。
分离公用代码
查看生成的js文件,我们发现有100k以上的size,这是因为我们编译时把react部分也编译进去了。
随着业务的进行,这个文件会越来越大,而react部分其实不怎么会变动,因此我们需要把它分离出来,以后只更新自己的业务js即可。
修改 webpack.prod.config.js ,增加一个入口:
module.exports = merge(common, { //.........., entry: { framework: ['react','react-dom'], }, //.........., });
再次build,我们会发现,react已经被打包成framework,但index没有缩小,这是由于我们尚未抽离公共部分所致。
添加代码至 webpack.prod.config.js :
module.exports = merge(common, { //............, optimization: { splitChunks: { chunks: 'all', minSize: 30000, maxSize: 0, minChunks: 1, cacheGroups: { framework: { test: "framework", name: "framework", enforce: true }, vendors: { priority: -10, test: /node_modules/, name: "vendor", enforce: true, }, } } }, });
cacheGroups对象,定义了需要被抽离的模块。
name是抽离后生成的名字,framework和入口文件模块名称相同,这样抽离出来的新生成的framework模块会覆盖被抽离的framework模块,虽然他们都叫framework。
vendors这个缓存组,它的test设置为 /node_modules/ 表示只筛选从node_modules文件夹下引入的模块,所以所有第三方模块才会被拆分出来。
再度运行
npm run build
看看效果把。
加载css
在src目录下新建app.css,并输入以下代码:
.App { height: 200px; display: flex; justify-content: center; align-items: center; background-color: lightblue; } h1 { font-size: 16px; color: white; }
打开app.js,添加以下代码:
import './app.css';
webpack只能编译js文件,css文件是无法被识别并编译的,我们需要loader加载器来进行预处理。
在终端输入以下指令:
npm install --save-dev css-loader mini-css-extract-plugin
css-loader这里就不做过多介绍了,mini-css-extract-plugin则是为了把css提取到单独的文件中。
在 webpack.prod.config.js 中添加下面代码:
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = merge(common, { //............, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[hash].css', chunkFilename: 'css/[id].[hash].css', }), new CleanWebpackPlugin() ], module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, //............, });
遇到后缀为.css的文件,webpack先用css-loader加载器去解析这个文件,遇到“@import”等语句就将相应样式文件引入(所以如果没有css-loader,就没法解析这类语句),最后计算完的css,再通过 MiniCssExtractPlugin 输出到相应的css文件中。
再度运行
npm run build
然后把所有文件上传到kintone的app中,看看效果把。
压缩代码
打开我们生成的所有文件,我们发现js是被压缩的,但css没有压缩,这是因为webpack没有配置默认的css压缩方案。
使用optimize-css-assets-webpack-plugin可以完成压缩,但很不幸,单独使用会覆盖js的压缩,所以必须配合TerserPlugin一起使用。
在终端输入:
npm install --save-dev optimize-css-assets-webpack-plugin terser-webpack-plugin
在 webpack.prod.config.js 中添加下面代码:
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); module.exports = merge(common, { //............, optimization: { minimizer: [ new TerserPlugin(), new OptimizeCssAssetsPlugin({ assetNameRegExp:/\.css$/g, cssProcessor:require("cssnano"), cssProcessorPluginOptions:{ preset:['default', { discardComments: { removeAll:true } }] }, canPrint:true }) ], //............, } //............, });
css的压缩处理器默认使用cssnano,压缩时会移除注释。
再度运行
npm run build
看看效果把。
配置开发环境
和生产环境不同,开发环境下,我不希望会打包出这么多的js和css,我希望1个js文件就行。
压缩会让我调试困难,而且我希望能有热更新的功能,不要每次都去run build。
这些要求可以通过配置我们的webpack.prod.config.js文件来实现。
安装devServer开发环境
webpack给我们提供了devServer开发环境,在终端输入以下代码:
npm install webpack-dev-server --save-dev
将下面代码添加到 webpack.dev.config.js 中:
const path = require('path'); const { merge } = require('webpack-merge'); const common = require('./webpack.common.config.js'); const webpack = require('webpack'); module.exports = merge(common, { mode: 'development', devServer: { contentBase: path.resolve(__dirname, '../dist'), open: true, port: 9000, compress: true, https: true, disableHostCheck: true, hot: true }, plugins: [ new webpack.HotModuleReplacementPlugin() ] });
通过上面的代码,我们配置了一个webserver服务器,利用HotModuleReplacementPlugin来实现热更新。
截止发稿,最新的webpack-dev-server版本为3.11.0,该版本存在bug,需要我们添加
disableHostCheck: true 来回避。
修改我们的package.json,加入启动代码:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config ./config/webpack.prod.config.js", "start": "webpack-dev-server --inline --config ./config/webpack.dev.config.js" },
至此,build命令对应生产环境,start命令对应开发环境。
先别急着启动,还有什么被遗忘了?
加载css
之前说过,webpack不能处理css。我们需要在 webpack.dev.config.js 中加载css处理模块。
生产环境中我们把css独立分离了出去,在开发环境中再这么做的话,意味着我们要在kintone里面填写2个url,js一个,css一个,有点麻烦。
利用 style-loader 可以解决上述问题。
在终端中输入:
npm install style-loader --save-dev
在 webpack.dev.config.js 中加入下面代码:
module.exports = merge(common, { //... module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] }, //... });
css-loader计算生成css,style-loader则会把生成的css放入style标签下。
loader是有顺序的,webpack肯定是先将所有css模块依赖解析完得到计算结果再创建style标签。因此应该把style-loader放在css-loader的前面。
在终端运行
npm run start
顺利启动服务后,把 https://localhost:9000/js/bundle.js 填到kintone app里面看看效果把。
生成ssl证书
dev-server启动后,那个大大的非安全证书认证是不是挺让人不爽的?为此我们需要利用mkcert来解决证书问题。
在终端中依次输入
mkcert -install # 根据要求输入Password mkcert localhost 127.0.0.1 ::1
之后你会发现在你的项目根目录下生成了localhost+2-key.pem和localhost+2.pem。
在 webpack.dev.config.js 中加入下面代码:
const fs = require('fs'); module.exports = merge(common, { //... devServer: { //... key: fs.readFileSync(path.resolve(__dirname, '../localhost+2-key.pem')), cert: fs.readFileSync(path.resolve(__dirname,'../localhost+2.pem')), //... } //... });
在终端再次运行
npm run start
你会发现烦人的非安全警告消失了。
其他
限于篇幅,@babel/plugin-transform-runtime、Less、Sass、图片加载、Eslint、TypeScript支持等功能不会在本篇中列出,感兴趣的朋友可以先自己研究。