使用npm發佈一個react組件(踩坑實踐)

前言

  • 本文主要記錄本人在使用 NPM 發佈具備樣式react組件 時的完整實踐流程,在這過程當中踩了許多坑,花在完善發佈腳手架的時間遠多於開發組件的時間,因而記錄下整個過程,但願能給你們提供幫助。css

  • 下文的內容主要包括:html

    • 發佈 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,
    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,當看到以下圖的結果,則表示編譯成功:
    編譯成功結
  • 此時訪問 localhost:3001則能夠看到下圖的結果:
    localhost結果

組件打包及功能測試

  • 完成組件的開發,再發包以前,須要對本身的組件的功能進行打包和測試。

組件打包

  • 前面已經在 package.json 中添加了打包組件的命令,然而,在執行 npm run build 以前,還須要在 package.json修改 main 字段,做用是聲明組件的入口文件。

開發者在 import 咱們的組件的時候會引入main 字段中 export 的內容。vue

  • 截止到目前爲止,個人 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
複製代碼
相關文章
相關標籤/搜索