从零搭建、开发和发布一个 npm 包(react + webpack + typescript + less)

近几日开发了一个习题渲染器(支持提交答案),内容好写,从0️建环境开发发布颇为不易😭,因此事后整理了一篇文章作个笔记。css

本文记录了项目的搭建、开发和发布过程,项目源码地址:githubhtml

目前有不少可优化的地方,好比添加 eslint、测试、npm publish hooks 等等,时间有限先发文章,后期(认真脸)会逐步完善 : )node

开发组件

因为组件比较简单,文章的顺序是先假设已经写好了简单的组件,而后须要什么就添加什么,一步步完成各类拓展。并非一开始就搭建环境接入各类拓展,万事俱备以后再写组件。感受本文的叙述对各类拓展的使用有更深入的理解。react

构建开发环境

建立项目目录并进入执行命令:webpack

$ yarn init
复制代码

填完几个选项后,会生成一个 package.json ,包含项目的基本信息。git

安装 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
复制代码

关于组件的源码,因为很简单而且不是本文的重点,因此就不展开了。可参阅项目组件部分源码github

安装配置 TS

因为使用了 TS,写好组件以后下一步要对 TS 文件进行编译。web

# 因为 TS 只会在开发环境使用,因此安装在 devDependencies
$ yarn add -D typescript
复制代码

须要在项目的根 / 目录新建一个 tsconfig.json 的配置文件,这样不用每次编译时输入重复复杂的命令。typescript

// 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_modulesbower_componentsjspm_packages<outDir> 目录。shell

关于 tsconfig.json 更多的配置说明,可查看:www.tslang.cn/docs/handbo…

开发示例 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.mdpackage.json 会被默认添加)。

npm 发布

发布以前必须在 npmjs.com 有帐号,若是没有,先注册:www.npmjs.com/signup。若是已经有了帐号,须要在本地终端执行 npm login 登录。输入用户名密码邮箱后便可登陆成功,npmjs.com 的帐户里会自动保存当前 pc 的惟一 token。

⚠️ 因为时间的关系,目前没有作好自动化。能够在 package.json 中添加一些 script 命令即 npm publish hook,用于在发布以前进行好比 preversionversionpostversion

发布

发布以前先要确保已经在生产模式下打包编译了该项目(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
复制代码

参考文档

tsconfig.json

React与webpack

相关文章
相关标签/搜索