從搭建腳手架到在npm上釋出react元件

JohnSnow發表於2019-02-16

從搭建腳手架到在npm上釋出react元件

最近公司給公司裡架設了私有的npm倉庫,相應地也需要一個用來發布react元件用的腳手架,在這個過程中又又又又複習了一下webpack,在這裡分享下腳手架搭建的過程。

首先,我們預期的腳手架具有如下功能

  • 開發元件時可以實時預覽
  • 對元件各種資源進行打包(js/css/圖片等)
  • 一鍵打包釋出

1.建立專案

腳手架的名字暫時取react-simple-component-boilerplate

首先建立一個新目錄用於放我們的檔案:

mkdir react-simple-component-boilerplate
cd react-simple-component-boilerplate

使用npm命令建立一個專案

npm init

接下來會提示你輸入專案的名稱、版本號、作者等,也可以一路回車,稍後修改。
這一步完成後,你的專案資料夾裡應該有一個package.json檔案了,這個檔案儲存了我們專案和元件的各種資訊。

接下來建立如下的目錄結構

react-simple-component-boilerplate
    |-- config // webpack配置
    |-- demo    // 開發時預覽用
    |-- dist    // 打包結果
    |-- src     // 原始檔目錄
        | -- assets // 存放圖片等媒體檔案
        | -- style    // 存放樣式,專案使用的是less來編寫樣式

2.安裝依賴

既然我們要釋出的是react元件,那依賴裡肯定少不了react。
使用npm install安裝下面的依賴

npm install react react-dom --save

打包工具選擇的是webpack,下面是開發依賴,也需要一併安裝

  "devDependencies": {
    // babel用於將你寫的es6+的程式碼轉換到es5
    "@babel/cli": "^7.0.0",
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.0.0", // 用於支援class屬性
    "@babel/plugin-proposal-decorators": "^7.0.0", // 支援decorator
    "@babel/plugin-transform-modules-commonjs": "^7.0.0",
    "@babel/plugin-transform-runtime": "^7.0.0", // 自動polyfill es5不支援的api特性
    "@babel/preset-env": "^7.0.0", // 根據目標環境來按需轉碼
    "@babel/preset-react": "^7.0.0", // 讓babel支援react語法
    "babel-loader": "^8.0.0",
    "css-loader": "^1.0.0",
    "file-loader": "^2.0.0",
    "html-loader": "^0.4.4",
    "less-loader": "^4.1.0", // 使用less來編寫樣式
    "mini-css-extract-plugin": "^0.5.0", // 將css提取成一個單獨的檔案
    "style-loader": "^0.23.0",
    "webpack": "^4.26.0",
    "webpack-cli": "^3.1.2", // webpack4之後需要額外安裝webpack-cli
    "webpack-dev-server": "^3.1.14", // 開發時預覽元件所用的服務,在檔案變化時會自動重新整理頁面
    "webpack-merge": "^4.1.4" // 用於合併webpack配置
  },

3.編寫元件

/src目錄下新建一個index.js,這就是我們元件的入口檔案了。
如果專案中要使用圖片、css等,分類放到assetsstyle資料夾下就好。

下面我們就在index.js中寫一個簡單的元件

/* src/index.js */

import React from `react`;
import `./style/style.less`; // 使用less的情況
import testPng from `./assets/test.png`; // 使用圖片的情況

export default class MyComponent extends Component {
    render(){
        return (<div>A new Component</div>)
    }
}

接下來,我們在/demo目錄下新建index.htmldemo.js這兩個檔案用於在開發元件時預覽元件效果。
index.html內容如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="root"></div>
<script src="demo.bundle.js"></script>
</body>
</html>

demo.js中,我們要使用一下剛剛寫的元件(位於/src/index.js)看一下效果,開發中這個demo.js檔案會被打包成demo.bundle.js,就是在上面index.html中引用的js。

import React from `react`;
import ReactDom from `react-dom`;
import MyComponent from `../src/index`

const Demo  = () => {
  return <div>
    <h1>元件預覽:</h1>
    <MyComponent />
  </div>
}

ReactDom.render(<Demo />, document.getElementById(`root`));

4.配置webpack和babel

4.1 配置webpack

/config下我們建立三個webpack配置檔案

  • webpack.base.js
  • webpack.config.dev.js // 開發時的配置
  • webpack.config.prod.js // 打包釋出時的配置

由於開發和釋出打包時webpack的配置有一部分是公共而且重複的,我們把這部分的配置單獨拿出來放到webpack.base.js中。
首先是公共配置webpack.base.js:

module.exports = {
  module: {
    rules: [
      { // 在webpack中使用babel需要babel-loader
        test: /.js?$/,
        loader: `babel-loader`,
        exclude: `/node_modules/`,
      },
      { // 用於載入元件或者css中使用的圖片
        test: /.(jpg|jpeg|png|gif|cur|ico|svg)$/,
        use: [{
          loader: `file-loader`, options: {
            name: "images/[name][hash:8].[ext]"
          }
        }]
      }
    ]
  }
}

下面是開發時所用的webpack配置,寫在webpack.config.dev.js

const path = require(`path`);
const merge = require(`webpack-merge`);
const baseConfig = require(`./webpack.base.js`); // 引用公共的配置

const devConfig = {
  entry: `./demo/demo.js`, // 入口檔案
  mode: `development`, // 打包為開發模式
  output: {
    filename: `demo.bundle.js`, // 輸出的檔名稱
    path: path.resolve(__dirname, `../demo`) // 輸出的檔案目錄
  },
  devServer: { // 該欄位用於配置webpack-dev-server
    contentBase: path.join(__dirname, `../demo`),
    compress: true,
    port: 9000, // 埠9000
    open: true // 自動開啟瀏覽器
  },
  module: {
    rules: [
      { // 編譯less
        test: /.less$/,
        exclude: `/node_modules/`,
        use: [{
          loader: `style-loader`
        }, {
          loader: `css-loader`
        }, {
          loader: `less-loader`
        }]
      },
    ]
  },
}

module.exports = merge(devConfig, baseConfig); // 將baseConfig和devConfig合併為一個配置

需要注意的是,等會使用webpack-dev-sevrer啟動開發服務時,並不會實際在demo資料夾下生成demo.bundle.js,打包好的檔案是在記憶體中的,但並不影響我們使用。

下面是打包釋出時所用的webpack配置,寫在webpack.config.prod.js

const path = require(`path`);
const merge = require(`webpack-merge`);
const baseConfig = require(`./webpack.base.js`);
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用於將元件的css打包成單獨的檔案輸出到`dist`目錄中

const devConfig = {
  entry: `./src/index.js`,
  mode: `production`,
  output: {
    path: path.resolve(__dirname, `../dist`),
    filename: `index.js`, // 輸出檔案
    libraryTarget: `umd`, // 採用通用模組定義, 注意webpack到4.0為止依然不提供輸出es module的方法,所以輸出的結果必須使用npm安裝到node_modules裡再用,不然會報錯
    library: `react-simple-component-boilerplate`, // 庫名稱
    libraryExport: `default`, // 相容 ES6(ES2015) 的模組系統、CommonJS 和 AMD 模組規範
  },
  externals: {
    react: {
      root: "React",
      commonjs2: "react",
      commonjs: "react",
      amd: "react"
    },
    "react-dom": {
      root: "ReactDOM",
      commonjs2: "react-dom",
      commonjs: "react-dom",
      amd: "react-dom"
    }
  },
  module: {
    rules: [{
      test: /.(le|c)ss$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        {
          loader: "less-loader",
          options: {
            sourceMap: false
          }
        }
      ]
    }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "main.min.css" // 提取後的css的檔名
    })
  ],
}

module.exports = merge(devConfig, baseConfig);

上面我們配置了externals欄位,這一點非常重要。
externals定義了外部依賴。將react和react-dom新增進該欄位,說明我們的元件將依賴外部的react和react-dom,這樣就可以避免把react和react-dom打包進去(不然元件會很大)

4.1 配置babel
我們需要用babel把我們的程式碼編譯成es5版本。在專案根目錄新建一個.babelrc檔案,輸入以下內容。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "> 0.25%, not dead"
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime",
    "@babel/plugin-transform-modules-commonjs",
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread"
  ]
}

我們在presets其中使用了preset-env, 規定了輸出的程式碼目標環境是份額大於0.25%的瀏覽器。另外由於我們的專案裡使用了reactpresets中就要加入preset-react
同時,plugins配置了一些babel外掛,用於支援裝飾器展開操作符等類內直接定義屬性等新的es特性。

4.3 配置啟動命令
我們再次回到專案根目錄下的package.json中,編輯如下

  "scripts": {
    "build": "set NODE_ENV=production && webpack --config ./config/webpack.config.prod.js",
    "pub": "npm run build && npm publish",
    "dev": "webpack-dev-server --config ./config/webpack.config.dev.js"
  },
  "main": "dist/index.js",
  "files": ["dist"]
  • build 命令用於打包元件
  • dev 命令會使用webpack-dev-server啟動一個開發服務用於預覽元件效果
  • pub 命令進行打包元件並且釋出到npm上

main欄位指定了我們的元件的入口檔案,files欄位用於指定我們的npm包的檔案目錄。

5.試用和釋出

要釋出一個npm包,我們需使用如下命令新增一個npm的賬號,如果已經新增過的這一步可以跳過。

npm adduser

如果已經有npm賬號,可以使用npm login登陸。
如果不知道自己是否已經新增過了npm賬號,使用npm whoami檢視登陸資訊即可

接下來就編輯package.json把元件的名稱/版本/介紹等欄位都填寫一下。

好了,接下我們先使用npm run dev命令,此時會自動開啟預設瀏覽器預覽元件。
如果沒什麼問題的話,接下來使用npm run pub進行打包和釋出。
等待發布完成後,我們就下載安裝一下。

npm i your-component // 假設你的包名字叫your-component

使用自己釋出的元件

import YourComponent from `your-component`;
import `your-component/dist/main.min.css`; // 如果給元件寫了樣式,需要手動匯入css檔案

6.總結

到這裡,一個非常非常簡單的用於釋出react小元件的腳手架就搭好了,總結一下其中要注意的地方:

  • webpack打包時libraryTarget要使用umd
  • externals 裡要把外部依賴配置好
  • 如果還要生成es module,可以額外使用gulp或rollup等工具
  • webpack4 之後建議使用MiniCssExtractPlugin來提取css

相關文章