近几日开发了一个习题渲染器(支持提交答案),内容好写,从0⃣️建环境开发发布颇为不易?,所以过后整理了一篇文章做个笔记。
本文记录了项目的搭建、开发和发布过程,项目源码地址:github。
目前有很多可优化的地方,比如添加 eslint、测试、npm publish hooks 等等,时间有限先发文章,后期(认真脸)会逐步完善 : )
开发组件
由于组件比较简单,文章的顺序是先假设已经写好了简单的组件,然后需要什么就添加什么,一步步完成各种拓展。并不是一开始就搭建环境接入各种拓展,万事俱备之后再写组件。感觉本文的叙述对各种拓展的使用有更深刻的理解。
构建开发环境
创建项目目录并进入执行命令:
$ yarn init
填完几个选项后,会生成一个 package.json
,包含项目的基本信息。
安装 React
$ yarn add react react-dom
开发组件代码
├─src // 用来存放组件源码
| ├─index.ts // 入口文件,用来暴露组件
| ├─utils.ts
| ├─types // TS 声明文件
| | ├─externals.d.ts // 由于项目中使用了 less,需额外声明才能使用模块化导入 less 文件
| | └index.ts
| ├─Renderer
| | ├─index.less
| | └index.tsx
关于组件的源码,由于很简单并且不是本文的重点,所以就不展开了。可参阅项目组件部分源码。
安装配置 TS
由于使用了 TS,写好组件之后下一步要对 TS 文件进行编译。
# 由于 TS 只会在开发环境使用,所以安装在 devDependencies
$ yarn add -D typescript
需要在项目的根 /
目录新建一个 tsconfig.json
的配置文件,这样不用每次编译时输入重复复杂的命令。
// tsconfig.json
{
"compilerOptions": {
"outDir": "./dist", // 输出的目录
"module": "CommonJS", // 指定生成哪个模块系统代码: "None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"
"target": "ES2015", // 指定 ES 目标版本,默认 ES3
"jsx": "react", // 在 .tsx 文件里支持 jsx
"declaration": true, // 生成相应的 .d.ts 文件
"removeComments": true, // 删除所有注释,除了以 /!* 开头的版权信息。
},
"include": [
"src/**/*", // 需要编译的文件
],
"exclude": [
"node_modules",
],
"files": []
}
使用 include
引入的文件可以使用 exclude
属性过滤。 然而,通过 files
属性明确指定的文件却总是会被包含在内,不管 exclude
如何设置。 如果没有特殊指定, exclude
默认情况下会排除 node_modules
,bower_components
,jspm_packages
和 <outDir>
目录。
关于 tsconfig.json
更多的配置说明,可查看:https://www.tslang.cn/docs/handbook/tsconfig-json.html
开发示例 example
为了方便开发, 我们可以在当前项目中创建一个 example(demo),开发时无需先将组件编译打包然后引用编译后的代码,而是可以直接引用该渲染器的源码,这样每次修改后保存便可以自动更新到页面,极大地提高了开发效率。当开发调试完成后,便可以使用生产模式,这样打包编译压缩后的渲染器代码便可直接使用。
使用方式:在 example 中直接 import /src/..
中的组件即可。
本习题渲染器 example 相关的代码目录结构如下所示:
├─example // 示例代码
| ├─src
| | ├─index.html // 用于挂载组件到页面
| | ├─index.tsx
| | └mock.ts // mock 数据
代码可参阅:example
引入 webpack
使用 webpack 完成对项目的打包编译并打开 example。
# webpack-dev-server 用来开启本地服务器打开 example
$ yarn add -D webpack webpack-cli webpack-dev-server
# 各种 loader
$ yarn add -D ts-loader less-loader style-loader css-loader
在项目根目录 /
中创建配置文件 webpack.config.js
用于打包编译。
// webpack.config.js
// TODO 只是开发环境的设置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const base = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
devtool: 'cheap-module-source-map',
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ['.ts', '.tsx', '.js', '.json'],
},
module: {
rules: [
// ts-loader 用于加载解析 ts 文件
{
test: /\.(ts|tsx)?$/,
loader: 'ts-loader',
exclude: /node_modules/
},
// 用于加载解析 less 文件
{
test: /\.less$/,
use: [
{ loader: 'style-loader', },
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[hash:base64:6]',
},
}
},
{ loader: 'less-loader', },
]
},
],
},
optimization: {
minimize: true, // 开启代码压缩
},
};
if (process.env.NODE_ENV === 'development') {
tempConfig = {
...base,
entry: path.join(__dirname, 'example/src/index.tsx'),
output: {
path: path.join(__dirname, 'example/dist'),
filename: 'bundle.js',
library: 'laputarenderer',
libraryTarget: 'umd',
},
plugins: [
// 自动注入编译打包好的代码至 html
new HtmlWebpackPlugin({
template: path.join(__dirname, './example/src/index.html'),
filename: 'index.html',
}),
],
devServer: {
// port: 8008, // example 的启动端口,选填
},
};
}
module.exports = tempConfig;
添加开发模式的 script 命令
添加命令之前,我们需要指定 node 执行环境,方便告知 webpack 现在是生产还是开发环境,决定应该如何打包。安装如下依赖,用来设置执行环境:
$ yarn add -D cross-env
现在我们需要添加一些 npm 执行命令,用来编译运行项目的 example。
// package.json
{
// ...
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --open"
},
// ...
}
ok,到现在为止,我们已经搭建好了这个习题渲染器的开发环境,接下来执行 yarn start
,编译完成之后浏览器会自动打开 example ,?️以开始为所欲为了~
开发调试完成之后,我们还需要在最终发布 npm 包之前使用生产模式编译压缩该渲染器。
搭建生产环境
修改 webpack 配置
首先安装一个删除文件依赖,方便每次打包时提前自动删除上一次编译打包后的文件。
# 用于打包编译之前清空 /dist
$ yarn add -D clean-webpack-plugin
修改 webpack.config.js
// webpack.config.js
// ...
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// ...
if (process.env.NODE_ENV === 'development') {
// ...
} else {
tempConfig = {
...base,
entry: './src/index.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
library: 'laputarenderer',
library: 'umd'
},
devtool: 'none',
// When importing a module whose path matches one of the following, just
// assume a corresponding global variable exists and use that instead.
// This is important because it allows us to avoid bundling all of our
// dependencies, which allows browsers to cache those libraries between builds.
// 我们想要避免把所有的React都放到一个文件里,因为会增加编译时间并且浏览器还能够缓存没有发生改变的库文件。
// 理想情况下,我们只需要在浏览器里引入React模块,但是大部分浏览器还没有支持模块。
// 因此大部分代码库会把自己包裹在一个单独的全局变量内,比如:jQuery或_。 这叫做“命名空间”模式,
// webpack 也允许我们继续使用通过这种方式写的代码库。
// 通过我们的设置"react": "React",webpack会神奇地将所有对"react"的导入转换成从React全局变量中加载
// 详情?请参阅本文末尾的参考文档:《React与webpack》
externals: {
'react': 'react',
'react-dom': 'react-dom'
},
plugins: [
new CleanWebpackPlugin(), // 编译之前清空 /dist
],
};
}
module.exports = tempConfig;
添加生产模式的 script 命令执行编译打包?
修改 package.json
// package.json
{
// ...
"scripts": {
"build": "cross-env NODE_ENV=production npx webpack"
},
// ...
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
}
⚠️注意:package.json
添加了 peerDependencies
字段,用来告诉其它想安装该库的项目:如果想使用我这个插件,就必须同级安装我指定的这些依赖。
在本项目中,指定了 react 和 react-dom,如果另外一个项目 A 想要安装此渲染器,就必须同时安装 react 和 react-dom。
npm1 和 npm2 版本可以安装渲染器的同时自动安装 peerDependencies 中的依赖,但是之后的版本需要手动安装,否则将会收到警告。
更多内容请阅读:Peer Dependencies。
自此,生产模式的所有准备工作都已经完成,执行 yarn build
之后会发现项目的根目录新增了 dist
文件夹,即最后要发布到 npm 上的文件。
接下来就要开始准备发布到 npm~
npm 发布准备
修改 package.json
配置
// package.json
{
"main": 'dist/index.js', // 该包的入口文件
"types": "dist/index.d.ts", // 指明声明文件的入口
// ...
"files": ["dist"], // npm 发布白名单
// ...
}
files
字段规定了:只有 dist 文件夹会出现在发布的包里(README.md
和 package.json
会被默认添加)。
npm 发布
发布之前必须在 npmjs.com 有账号,如果没有,先注册:www.npmjs.com/signup。如果已经有了账号,需要在本地终端执行 npm login
登陆。输入用户名密码邮箱后即可登录成功,npmjs.com 的账户里会自动保存当前 pc 的唯一 token。
⚠️ 由于时间的关系,目前没有做好自动化。可以在package.json
中添加一些 script 命令即 npm publish hook,用于在发布之前进行比如preversion
、version
和postversion
。
发布
发布之前先要确保已经在生产模式下打包编译了该项目(yarn build),做了自动化的当我没说 : P
$ npm publish
等待发布成功之后便可以在 npmjs.com 的个人主页查看新发布的包啦 ???
更新版本
修改项目之后更新版本:
# 升级补丁版本号 1.0.0 -> 1.0.1
$ npm version patch
# 升级次版本号 1.0.0 -> 1.1.0
$ npm version minor
# 升级主版本号 1.0.0 -> 2.0.0
$ npm version major
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。