乐趣区

关于javascript:从零封装一个组件库并联调发布

这段时间很闲,”领导“便对咱们前端提了点要求心愿咱们能有本人的组件库,美其名曰:开发提高效率,对立款式,减轻负担。嗯~,的确是很闲!

抱着这是领导的嘱咐本人又是小菜鸡想提高的想法,决定试试,哪怕玩玩也好的态度开始了这次的旅程;
在网上晃荡了一上午,博客文章一大堆,多是头重脚轻,又来没回的,没方法兴许都是大神笔记吧,只能本人来搞搞了,总是要本人弄的,兴许我写的还不如人家呢!
闲言少叙,咱们这就开始!

第一步 筹备工作

新建一个文件夹,关上命令行输出 npm init,

$ npm init
name: (wq-components)
version: (1.0.0) 0.1.0
description: an example component library with React!
entry point: (index.js) 
test command:
git repository:
keywords:
Author: machinish_wq
license: (ISC)MIT
About to write to /Users/alanbsmith/personal-projects/trash/package.json:

{
  "name": "wq-components",
  "version": "0.1.0",
  "description": "an example component library with React!",
  "main": "dist/index.js",
  "scripts": {"test": "echo \"Error: no test specified\"&& exit 1"},
  "author": machinish_wq,
  "license": "MIT"
}

Is this ok? (yes)

在根目录下增加以下配置文件
touch pubilc/index.html script/ .babelrc .gitignore .npmignore README.md

  • pubilc/index.html 寄存 root 模板
  • script/ 文件夹寄存 webpack.config 相干文件
  • .babelrc 蕴含编译阶段一些有用的转转码规定(presets)
  • .gitignore 和.npmignore 别离用于疏忽来自 git 和 npm 的文件
  • README.md 也十分重要。这是咱们和开源社区交换的次要形式

pubilc/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React</title>
  </head>
  <body>
    <div id="root" class="root"></div>
  </body>
</html>

script/webpack.dev.config.js

const path = require('path');
const webpack = require('webpack');
const webpackConfigBase = require('./webpack.base.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {merge} = require('webpack-merge');

function resolve(relatedPath) {return path.join(__dirname, relatedPath)
}

const webpackConfigDev = {
  mode: 'development',

  entry: {app: [resolve('../src/index.js')],
  },

  output: {path: resolve('../lib'), 
    filename: 'button.js',
  },

  devtool: 'cheap-module-eval-source-map',   

  devServer: { // 本地服务器
    contentBase: resolve('../lib'), 
    hot: true,
    open: true,   
    host: 'localhost',
    port: 8080,
  },

  plugins: [new HtmlWebpackPlugin({template: './public/index.html',}),
    new webpack.NamedModulesPlugin(),  
    new webpack.HotModuleReplacementPlugin()]
}

module.exports = merge(webpackConfigBase, webpackConfigDev)

script/webpack.prod.config.js

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const webpackConfigBase = require('./webpack.base.config');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const {merge} = require('webpack-merge');

function resolve(relatedPath) {return path.join(__dirname, relatedPath)
}

const webpackConfigProd = {
  mode: 'production',

  entry: {app: [resolve('../src/components/index.js')],
  },

  output: {
    filename: 'index.js',
    path: resolve('../lib'),
    library: { // 导出的库名
      root: "componentLibrary",
      amd: "component-library",
    },
    libraryTarget: "umd"
  },

  devtool: 'source-map',  // 或应用 'cheap-module-source-map'、'none'
  optimization: {
    minimizer: [
      // 压缩 js 代码
      new TerserJSPlugin({// 多过程压缩
        parallel: 4,// 开启多过程压缩
        terserOptions: {
          compress: {drop_console: true,   // 删除所有的 `console` 语句},
        },
      }),
      // 压缩 css 代码
      new OptimizeCSSAssetsPlugin()],
  },
  externals: [nodeExternals()],

  plugins: [new CleanWebpackPlugin() // 每次执行都将清空一下./lib 目录
  ]
}
module.exports = merge(webpackConfigBase, webpackConfigProd)

注: libraryTarget 和 library 是开发类库必须要用的输入属性

开发库被援用的形式有一下几种:’

  1. 传统的 script 形式:

    <script src="demo.js"></script>
  2. AMD 形式:

    define(['demo'], function(demo) {demo();
    });
    
  3. commonjs 形式:

    const demo = require('demo');
    demo();
    
  4. ES6 模块引入

    import demo from 'demo';

    类库为什么反对不同形式的引入?这就是 webpack.library 和 output.libraryTarget 提供的性能。
    output.libraryTarget 属性是管制 webpack 打包的内容如何被裸露的。
    裸露的形式分为以下三种形式:

  • 裸露一个变量
    libraryTarget:“var”
    webpack 打包进去的值赋值给一个变量,该变量名就是 output.library 指定的值。将打包后的内容复制给一个全局变量,援用类库的时候间接应用该变量,nodejs 环境不反对。
  • 通过对象属性裸露
    libraryTarget:“this”
    libraryTarget: “window”
    libraryTarget: “global” (此状况反对 node)
    以上三种办法是在公共对象上 export 出你的办法函数。
    长处:缩小变量抵触
    毛病:nodejs 环境不反对
  • 通过模块裸露
    1、libraryTarget: “commonjs”
    间接在 exports 对象上导出–定义在 library 上的变量,node 反对,浏览器不反对
    2、libraryTarget: “commonjs2”
    间接用 module.exports 导出,会疏忽 library 变量,node 反对,浏览器不反对,这个选项能够应用在 commonjs 环境中。
    为什么 commonjs 不须要独自引入 requirejs?
    commonjs 是服务端模块化语言标准,在 node 中应用的时候会应用 node 中的 requireJS。
    3.libraryTarget: “amd”
    amd 属于客户端模块语言的标准,须要用户本人引入 requirejs 能力应用。不反对 nodejs 环境,反对浏览器环境。
    4.libraryTarget: “umd” 咱们要选用这个
    该计划反对 commonjs、commonjs2、amd,能够在浏览器、node 中通用。它会依据援用该插件的上下文来判断属于什么环境,使其和 CommonJS、AMD 兼容或者裸露为全局变量

script/webpack.base.config.js loader 被抽离档在这里方便管理和配置

const path = require('path');
function resolve(relatedPath) {return path.join(__dirname, relatedPath)
}
const webpackConfigBase = {
  resolve: {
    alias: {"@": resolve("src")
    },
    // 要解析的文件的拓展名
    extensions: [".js", ".jsx", ".json"],
    // 解析目录时须要应用的文件名
    mainFiles: ["index"]
  },
  //module 此处为 loader 区域,个别文件内容解析,解决放在此处,如 babel,less,postcss 转换等
  module: {
    rules: [
      {test: /\.js[x]?$/,  // 用正则来匹配文件门路,这段意思是匹配 js 或者 jsx
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {presets: ['@babel/preset-react'],
          }
        }
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          {loader: 'css-loader', options: { importLoaders: 1} },
          'less-loader',
          {loader: 'less-loader', options: { javascriptEnabled: true} }
        ]
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: (loader) => [require('autoprefixer')()],
            }
          }
        ]
      }
    ]
  }
}
module.exports = webpackConfigBase;

.babelrc这里配置的是 antd 的按需加载

{"presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread",
      ["import", {"libraryName": "antd", "libraryDirectory": "lib", "style":"css"}],
  ]
}

.gitignore 增加不须要 push 的文件

.DS_Store
dist
node_modules
*.log

.npmignore 用于疏忽不必公布到 npm 的文件

.babelrc
src
CODE_OF_CONDUCT.md
node_modules
.gitignore
webpack.config.js
yarn.lock
.eslintrc

第二步 增加配置

2.1 配置 git 和 npm

在初始化的 package.json 文件里增加以下命令

 "scripts": {
    "build": "webpack --config ./scripts/webpack.prod.config.js",
    "dev": "webpack-dev-server --config ./scripts/webpack.dev.config.js",
    "prepublish": "npm run build"
 },
 // "files": [
 //   "lib"
 // ],
  • build 将运行 scripts 目录下 webpack.prod.config.js 文件对 src 目录下的内容如何进行转码而后导出到 webpack 事后配置的打包目录下。须要在 webpack.prod.config.js 文件设置入口 src/index.js。
  • npm 会在咱们运行 npm publish 之前执行这个脚本。这将确保咱们在 dist 的资源是最新的版本。
  • “files” 属性的值是一个数组,内容是模块下文件名或者文件夹名, 也能够在模块根目录下创立一个 ”.npmignore” 文件(windows 下无奈间接创立以 ”.” 结尾的文件,应用 linux 命令行工具创立如 git bash),写在这个文件里边的文件即使被写在 files 属性里边也会被排除在外,写法与 ”.gitignore” 相似(自己并没有应用)

2.2 装置依赖

 "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.10.4",
    "@babel/plugin-proposal-class-properties": "^7.2.3",
    "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
    "@babel/preset-env": "^7.10.4",
    "@babel/preset-react": "^7.10.4",
    "babel-plugin-import": "^1.13.3",
    "@hot-loader/react-dom": "^16.13.0",
    "autoprefixer": "^9.8.4",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.6.0",
    "html-webpack-plugin": "^4.3.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "style-loader": "^1.2.1",
    "terser-webpack-plugin": "^3.0.6",
    "less": "^3.10.2",
    "less-loader": "^5.0.0",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.0.9",
    "webpack-node-externals": "^2.5.0"
  },
  "dependencies": {
    "react": "^16.14.0",
    "react-dom": "^16.14.0",
    "antd": "^4.15.1"
  },
  "peerDependencies": {
    "prop-types": "^15.7.2",
    "react": "^16.14.0",
    "react-dom": "^16.14.0",
    "antd": "^4.15.1"
  },
  • dependencies 字段指定了我的项目运行所依赖的模块,该类型依赖个别属于运行我的项目业务逻辑须要依赖的第三方库。
  • devDependencies 指定我的项目开发所须要的模块。
  • peerDependencies 中申明的依赖,如果我的项目没有显式依赖并装置,则不会被 npm 主动装置,转而输入 warning 日志,通知我的项目开发者,你须要显式依赖了,不要再依附我了。

2.3 增加组件

创立 src/components 目录,在目录下顺次增加 button.js、button.css 文件
button.js

import React, {useState} from "react";
import {Button} from 'antd'
import "./change_button.less";
import 'antd/dist/antd.less';

const ChangeButton = (props) => {const [btnTxt, setBtnTxt] = useState("Login");
  return (
    <React.Fragment>
      <div
        className={"btn"}
        onClick={() => { setBtnTxt(btnTxt === "Login" ? "Logout" : "Login"); }} >
        <span>{btnTxt}</span>
      </div>
      <Button type={"primary"} onClick={() => console.log("终于进去了")}> 你好啊看到我开心吗 </Button>
    </React.Fragment>
  );
};

export default ChangeButton;

button.less

.button-container {
  width: 100px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: aquamarine;
  border-radius: 5px;
}

.button-container:hover {cursor: pointer;}

src/index.js

import MyButton from './button';

export default MyButton;

2.4 启动我的项目

运行 npm start 启动我的项目,

我的项目顺利启动,先一步本地联调

第三步 本地调试

在之前的组件库根目录运行 pwd 获取我的项目地址;
而后,咱们须要在本地利用脚手架或者手动新建一个小利用我的项目;并在以后我的项目根目录运行
npm link [组件库地址]

不出以外你会遇到一个 Maximum call stack size exceeded 谬误!没遇到那当我没说,手动难堪!
办法是删除组件的原有依赖,并清空 npm 全局缓存,再次尝试命令 npm link [组件库地址],如屡次尝试未果,则须要批改权限:指令sudo chown -R 501:20 "/Users/[用户名]/.npm"
之后你在本人的本地我的项目的 node_modules 找到本人的组件库;

这里是我本人在批改那个问题的时候把组件库的名字改了!小细节疏忽即可!
注:这里须要把 lib 文件下打包后的 index 挪到跟文件下来,不然在接下来的调试中会报错找不到这个组件库,看了下 antd 和其余的依赖这里是能够没有的只是我还没去找对应的解决办法
之后咱们就能够像援用其余的组件一样失常应用咱们的组件了!

第四步 上传 npm

运行以下命令将组件公布到 npm

npm publish

输出 npm 账号密码即可,没有的记得申请一个

看着挺简略,操作起来各种问题频发,就当是个笔记吧欢送交换斧正!

退出移动版