使用npm釋出一個react元件(踩坑實踐)

前端小黑發表於2019-08-30

前言

  • 本文主要記錄本人在使用 NPM 釋出具有樣式react元件 時的完整實踐流程,在這過程中踩了許多坑,花在完善釋出腳手架的時間遠多於開發元件的時間,於是記錄下整個過程,希望能給大家提供幫助。

  • 下文的內容主要包括:

    • 釋出 react元件 的腳手架搭建。
    • 開發元件。
    • 打包元件,並在測試專案中引入打包元件模組,驗證元件功能。
    • 釋出到 NPM

腳手架搭建

建立專案

  • 首先建立專案資料夾,並使用 npm init 命令生成初始的配置檔案 package.json,具體命令如下:
mkdir react-component-npm-cli
cd react-component-npm-cli
npm init
複製程式碼
  • 在使用 npm init 命令 的時候,會提示我們輸入專案的名稱、版本號、作者等,可以一路回車,稍後進行修改。或者直接使用下面的命令採用預設配置,後面再根據自己的需要進行修改:
npm init -y
複製程式碼
  • 這一步完成後,我們的專案資料夾裡就會新增一個 package.json 檔案。接下來完善腳手架,建立如下的目錄結構:
├── config  # webpack配置
     ├── webpack.base.js # 公共配置
     ├── webpack.dev.config.js # 開發環境配置
     └── webpack.prod.config.js # 打包釋出環境配置
├── example # 開發時預覽程式碼
       ├── src # 示例程式碼目錄
            ├── app.js # 入口 js 檔案
            └── index.html # 入口 html 檔案
├── lib # 元件打包結果目錄
├── node_modules # 安裝依賴時自動生成
├── src # 元件原始碼目錄
     ├── index.css # 元件樣式
     └── index.js  # 元件原始碼
├── .babelrc # babel 配置
├── .npmignore # 指定釋出 npm 的時候需要忽略的檔案和資料夾
├── README.md
└── package.json
複製程式碼

安裝依賴

  • 首先安裝 react 相關的依賴:
npm i react react-dom -D
複製程式碼
  • 接著安裝 babel 編譯相關的依賴:
npm i @babel/cli @babel/core @babel/preset-env @babel/preset-react -D
複製程式碼
  • 本專案採用 webpack 做構建,同時使用 webpack-dev-server 作為本地開發伺服器,使用 webpack-merge 合併webpack配置,因此使用下面的命令安裝相關依賴:
npm i webpack webpack-cli webpack-dev-server webpack-merge -D
複製程式碼
  • 同時,使用 babel-loader 編譯 jsx,使用 MiniCssExtractPlugin 提取 css,安裝命令如下:
npm i babel-loader mini-css-extract-plugin -D
複製程式碼
  • 最後安裝 style-loadercss-loader ,二者組合在一起使我們能夠把樣式表嵌入 webpack 打包後的 js 檔案中(開發環境使用)。
  • 執行完以上命令,此時 package.json 中包含的依賴資訊如下:
{
    "devDependencies": {
    // babel 用於將 es6+ 的程式碼轉換成 es5
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5", // 根據目標環境實現按需轉碼
    "@babel/preset-react": "^7.0.0", // 讓babel支援react語法
    "babel-loader": "^8.0.6", // 編譯 jsx
    "css-loader": "^3.2.0", // 將 css 裝換成js
    "mini-css-extract-plugin": "^0.8.0", // 提取css
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "style-loader": "^1.0.0", // 將 css 裝換成js
    "webpack": "^4.39.3",
    "webpack-cli": "^3.3.7", // webpack4之後需要額外安裝webpack-cli
    "webpack-dev-server": "^3.8.0", // 開發時預覽元件所用的服務,在檔案變化時會自動重新整理頁面
    "webpack-merge": "^4.2.2" // 用於合併webpack配置
  },
  "dependencies": {}
}
複製程式碼

配置 webpack 和 babel

  • 如前面給出的目錄結構所示,在 config 目錄下我們建立 3 個 webpack 配置檔案:
    • 公共配置檔案:webpack.base.js
    • 開發環境配置檔案:webpack.dev.config.js
    • 打包釋出環境配置檔案:webpack.prod.config.js
  • 由於開發和釋出打包時 webpack 配置有一部分是公共而且重複的,所以把這部分的配置單獨拿出來放到webpack.base.js中,該檔案內容如下:
module.exports = {
  module: {
    rules: [
      {
        // 使用 babel-loader 來編譯處理 js 和 jsx 檔案
        test: /\.(js|jsx)$/,
        use: "babel-loader",
        exclude: /node_modules/
      }
    ]
  },
};
複製程式碼
  • 開發時採用的 webpack 配置寫在 webpack.dev.config.js 中,內容如下:
const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.js'); // 引用公共配置

const devConfig = {
  mode: 'development', // 開發模式
  entry: path.join(__dirname, "../example/src/app.js"), // 專案入口,處理資原始檔的依賴關係
  output: {
    path: path.join(__dirname, "../example/src/"),
    filename: "bundle.js", // 使用webpack-dev-sevrer啟動開發服務時,並不會實際在`src`目錄下生成bundle.js,打包好的檔案是在記憶體中的,但並不影響我們使用。
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: /\.min\.css$/,
        loader: ['style-loader','css-loader?modules'],
      },
      {
        test: /\.min\.css$/,
        loader: ['style-loader','css-loader'],
      },
    ]
  },
  devServer: {
    contentBase: path.join(__dirname, '../example/src/'),
    compress: true,
    host: '127.0.0.1', // webpack-dev-server啟動時要指定ip,不能直接通過localhost啟動,不指定會報錯
    port: 3001, // 啟動埠為 3001 的服務
    open: true // 自動開啟瀏覽器
  },
};
module.exports = merge(devConfig, baseConfig); // 將baseConfig和devConfig合併為一個配置
複製程式碼
  • 打包元件時採用的 webpack 配置寫在 webpack.prod.config.js 中,內容如下:
const path = require('path');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.js'); // 引用公共的配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 用於將元件的css打包成單獨的檔案輸出到`lib`目錄中

const prodConfig = {
  mode: 'production', // 開發模式
  entry: path.join(__dirname, "../src/index.js"),
  output: {
    path: path.join(__dirname, "../lib/"),
    filename: "index.js",
    libraryTarget: 'umd', // 採用通用模組定義
    libraryExport: 'default', // 相容 ES6 的模組系統、CommonJS 和 AMD 模組規範
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: [MiniCssExtractPlugin.loader,'css-loader?modules'],
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "main.min.css" // 提取後的css的檔名
    })
  ],
  externals: { // 定義外部依賴,避免把react和react-dom打包進去
    react: {
      root: "React",
      commonjs2: "react",
      commonjs: "react",
      amd: "react"
    },
    "react-dom": {
      root: "ReactDOM",
      commonjs2: "react-dom",
      commonjs: "react-dom",
      amd: "react-dom"
    }
  },
};

module.exports = merge(prodConfig, baseConfig); // 將baseConfig和prodConfig合併為一個配置
複製程式碼
  • 完成 webpack 配置檔案的編寫之後,需要在 package.json 中的scripts 欄位中配置相關的啟動、打包和釋出命令,在 package.json 中新增的指令碼內容如下:
// package.json
...
  "scripts": {
    "start": "webpack-dev-server --config config/webpack.dev.config.js", // 使用webpack-dev-server啟動一個開發服務用於預覽元件效果
    "build": "webpack --config config/webpack.prod.config.js", // 打包元件
    "pub": "npm run build && npm publish", // 打包元件併發布到npm
  },
...
複製程式碼

配置 babel

  • 我們需要使用 babel 把我們的程式碼編譯成 es5 版本。在專案根目錄下建好的 .babelrc檔案內補充以下內容:
{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}
複製程式碼
  • 以上則完成了腳手架的搭建過程,接下來就可以開始開發元件。

開發元件

  • 為了演示,我將在 src 目錄中建立一個簡單的具有樣式的元件,大家可根據自己的需要進行修改,index.js 檔案的具體內容如下:
<!-- src/index.js -->
import React from 'react';
import * as styles from './index.css';
class ReactDemo extends React.Component{
  render () {
    return <div className={styles.wrapper}>hello world</div>
  }
}
export default ReactDemo;
複製程式碼
  • 樣式檔案 index.css 檔案的具體內容如下:
<!-- src/index.css -->
.wrapper{
  background: red;
}
複製程式碼
  • 為了在開發時實時預覽元件的效果,則需要在 example 目錄下新建 index.htmlapp.js 兩個檔案, index.html 的內容如下:
<!-- examples/src/index.html -->
<html>
<head>
    <title>My First React Component</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
  <div id="root"></div>
  <script src="bundle.js"></script> <!-- 這句十分重要 -->
</body>
</html>
複製程式碼
  • 細心的同學會⚠️注意⚠️到, index.html 檔案中引入了檔名為 bundle.js 的指令碼,原因是在開發過程(使用webpack-dev-sevrer啟動開發服務)時,並不會實際在 src 目錄下生成 bundle.js,打包好的檔案是在記憶體中的,如果要實時預覽效果,需要在 html 中引入(也可使用html-webpack-plugin外掛注入,這裡不進行具體講解)。
  • app.js 的內容如下:
/*** examples/src/app.js ***/
import React from 'react'
import { render } from 'react-dom'
import ReactDemo from '../../src' // 引入元件

const App = () => <ReactDemo />
render(<App />, document.getElementById('root'))
複製程式碼
  • 現在在根目錄執行 npm start,當看到如下圖的結果,則表示編譯成功:
    編譯成功結
  • 此時訪問 127.0.0.1:3001則可以看到下圖的結果:

使用npm釋出一個react元件(踩坑實踐)

元件打包及功能測試

  • 完成元件的開發,再發包之前,需要對自己的元件的功能進行打包和測試。

元件打包

  • 前面已經在 package.json 中新增了打包元件的命令,然而,在執行 npm run build 之前,還需要在 package.json修改 main 欄位,作用是宣告元件的入口檔案。

開發者在 import 我們的元件的時候會引入main 欄位中 export 的內容。

  • 截止到目前為止,我的 package.json 完整內容如下,為便於理解,我加上註釋,如果要直接使用刪掉註釋即可:
{
  "name": "react-demo-component", // 釋出的 npm 包的名字,確保獨一無二(先在 npm 上搜,若與已有的包重名會報錯)
  "version": "1.0.0", // npm 包版本
  "description": "A test component demo", // 包的描述
  "main": "lib/index.js", // *重點*:宣告元件的入口檔案
  "scripts": { // 執行指令碼
    "start": "webpack-dev-server --config config/webpack.dev.config.js", // 使用webpack-dev-server啟動一個開發服務用於預覽元件效果
    "build": "webpack --config config/webpack.prod.config.js", // 打包元件
    "pub": "npm run build && npm publish", // 打包元件併發布到npm
  },
  "keywords": [ // 關鍵字(在npm網站上搜尋 npm 包的關鍵詞)
    "react",
    "hello world"
  ],
  "author": "feSmallBlack", // npm 包的作者
  "license": "ISC", // 版權許可證
  "devDependencies": { // 開發環境依賴
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.2.0",
    "mini-css-extract-plugin": "^0.8.0",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "style-loader": "^1.0.0",
    "webpack": "^4.39.3",
    "webpack-cli": "^3.3.7",
    "webpack-dev-server": "^3.8.0",
    "webpack-merge": "^4.2.2"
  },
  "dependencies": {} 生產環境依賴
}
複製程式碼
  • 接下來,則可以使用 npm run build 打包我們開發好的元件,出現下面的結果則代表打包成功:

打包成功的結果

  • 此時,專案根目錄的 lib 目錄下會出現以下兩個檔案:
    lib目錄下的內容
  • 這兩個檔案則是我們要釋出的包。

元件功能測試

  • 雖然躍躍欲試忍不住要發包,但是!在發包之前,為了對大家和自己負責,避免頻繁的撤包操作,最好就是先對自己的元件進行試用。
  • 我們可以使用 npm link 把打包之後的元件引入到全域性 node_modules 中,相關命令如下:
// At development directory
npm run build
npm link
複製程式碼
  • 執行 npm link 命令後的結果如下:
    npm link的結果
  • example/src/app.js 檔案中進行試用,相關命令如下:
// At development directory
cd example/src
npm link react-demo-component
複製程式碼
  • 然後修改 example/src/app.js 的內容:
/*** examples/src/app.js ***/
import React from 'react'
import { render } from 'react-dom'
import ReactDemo from 'react-demo-component';
import 'react-demo-component/lib/main.min.css'; // !需要引入樣式!
// import ReactDemo from '../../src'

const App = () => <ReactDemo />
render(<App />, document.getElementById('root'))
複製程式碼
  • 出現下面的結果,則代表打包的元件功能驗證通過:
    驗證功能結果
  • 接下來將介紹喜聞樂見的最後一個環節,釋出元件到 NPM

釋出元件

配置.npmignore

  • 如果專案中沒有編寫 .npmignore 檔案,則需要在 package.json新增 files 欄位,用於申明將要釋出到 NPM 的檔案。如果省略掉這一項,所有檔案包括原始碼會被一起上傳到 NPM
  • 本文采用寫 .npmignore 檔案的方式,實現僅釋出打包後的元件程式碼。 .npmignore 檔案的具體內容如下:
# 指定釋出 npm 的時候需要忽略的檔案和資料夾
# npm 預設不會把 node_modules 發上去
config # webpack配置
example # 開發時預覽程式碼
src # 元件原始碼目錄
.babelrc # babel 配置
複製程式碼

註冊 npm 賬號

  • 要釋出一個 npm 包,我們需使用如下命令新增一個 npm 的賬號(如果已經新增過的這一步可以跳過):
npm adduser
複製程式碼
  • 按提示資訊輸入自己的使用者名稱密碼郵箱後,如果命令列輸出Logged in as <user> on https://registry.npmjs.org/,則說明賬號註冊並登陸成功。npm 會把登陸資訊記錄並暫存在 /Users/<user>/.npmrc 配置檔案中。
  • 如果已經有 npm 賬號,可以直接使用 npm login 登入。
  • ⚠️注意:由於國內使用 npm 官方源安裝包的時候比較慢,基本上在國內開發都會修改 npm 源地址,在發包之前,一定要切換到 npm 源才可以,不然就會報出如下錯誤:
error: no_perms Private mode enable, only admin can publish this module
複製程式碼
  • 可以使用npm config list檢視當前使用的源地址,如果不是官方源地址,則可以通過下面的命令切換 npm 源:
npm config set registry http://registry.npmjs.org
複製程式碼
  • 也可以使用 nrm 來進行 npm 的源管理,礙於本文篇幅,這裡就不再進行解釋(傳送門?:使用NRM進行NPM的源管理
  • 成功切換到官方源後,則可以使用下面命令將我們的元件釋出到 NPM
npm run pub
// 上面的命令效果與下面的命令效果一樣
npm build
npm publish
複製程式碼
  • 當看到下面的結果,則表示發包成功:
    發包成功結果
  • 此時在 npm 上也可以檢視你剛釋出的包:
    npm結果
  • 別人也可以使用下面的命令下載你的包:
npm i react-demo-component // 假設你的包名字叫react-demo-component
複製程式碼
  • 使用方法:
// 元件中引入
import ReactDemo from 'react-demo-component';
// 如果給元件寫了樣式,需要手動匯入css檔案
import 'react-demo-component/lib/main.min.css';
複製程式碼
  • 取消釋出(最好不要,別人可能下載了你的包):
npm unpublish react-demo-component --force // 假設你的包名字叫react-demo-component
複製程式碼

相關文章