react组件从搭建脚手架到在npm发布的步骤实现


Posted in Javascript onJanuary 09, 2019

最近公司给公司里架设了私有的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等,分类放到assets、style文件夹下就好。

下面我们就在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.html和demo.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%的浏览器。另外由于我们的项目里使用了react,presets中就要加入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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript Date对象使用总结
May 14 Javascript
基于jQuery判断两个元素是否有重叠部分的代码
Jul 25 Javascript
node.js中的console.log方法使用说明
Dec 09 Javascript
JavaScript判断前缀、后缀是否是空格的方法
Apr 15 Javascript
JS实现黑客帝国文字下落效果
Sep 01 Javascript
javascript中setAttribute兼容性用法分析
Dec 12 Javascript
浅谈struts1 &amp; jquery form 文件异步上传
May 25 jQuery
jQuery动态添加.active 实现导航效果代码思路详解
Aug 29 jQuery
vue实现自定义多选与单选的答题功能
Jul 05 Javascript
webpack 如何解析代码模块路径的实现
Sep 04 Javascript
浅析vue-router中params和query的区别
Dec 24 Javascript
JS Object构造函数之Object.freeze
Apr 28 Javascript
微信小程序公用参数与公用方法用法示例
Jan 09 #Javascript
微信小程序实现的日期午别医生排班表功能示例
Jan 09 #Javascript
Windows下Node爬虫神器Puppeteer安装记
Jan 09 #Javascript
jQuery简单实现根据日期计算星期几的方法
Jan 09 #jQuery
jQuery实现根据身份证号获取生日、年龄、性别等信息的方法
Jan 09 #jQuery
爬虫利器Puppeteer实战
Jan 09 #Javascript
puppeteer库入门初探
Jan 09 #Javascript
You might like
PHP goto语句简介和使用实例
2014/03/11 PHP
php操作memcache缓存方法分享
2015/06/03 PHP
PHP中使用jQuery+Ajax实现分页查询多功能操作(示例讲解)
2017/09/17 PHP
摘自百度的图片轮换效果代码
2007/11/19 Javascript
JavaScript 错误处理与调试经验总结
2010/08/10 Javascript
JavaScript框架(iframe)操作总结
2014/04/16 Javascript
使用angularjs创建简单表格
2016/01/21 Javascript
jQuery EasyUI右键菜单实现关闭标签/选项卡
2016/10/10 Javascript
JS实现微信摇一摇原理解析
2017/07/22 Javascript
详谈js中标准for循环与foreach(for in)的区别
2017/11/02 Javascript
解决v-for中使用v-if或者v-bind:class失效的问题
2018/09/25 Javascript
Vue2.0使用嵌套路由实现页面内容切换/公用一级菜单控制页面内容切换(推荐)
2019/05/08 Javascript
一起写一个即插即用的Vue Loading插件实现
2019/10/31 Javascript
解决Echarts2竖直datazoom滑动后显示数据不全的问题
2020/07/20 Javascript
Python实现删除文件但保留指定文件
2015/06/21 Python
Python使用Pickle库实现读写序列操作示例
2018/06/15 Python
pandas数据集的端到端处理
2019/02/18 Python
Python3.5 Pandas模块之DataFrame用法实例分析
2019/04/23 Python
利用python-pypcap抓取带VLAN标签的数据包方法
2019/07/23 Python
详解python中index()、find()方法
2019/08/29 Python
学python爬虫能做什么
2020/07/29 Python
Pycharm制作搞怪弹窗的实现代码
2021/02/19 Python
详解Sticky Footer 绝对底部的两种套路
2017/11/03 HTML / CSS
Amara德国:家居饰品、设计师品牌和豪华礼品
2019/05/20 全球购物
Moda Italia荷兰:意大利男士服装
2019/08/31 全球购物
校园十佳歌手策划书
2014/01/22 职场文书
反邪教警示教育方案
2014/05/13 职场文书
平安家庭示范户事迹
2014/06/02 职场文书
文明好少年事迹材料
2014/08/19 职场文书
三好学生事迹材料
2014/12/24 职场文书
财务会计岗位职责
2015/02/03 职场文书
煤矿隐患排查制度
2015/08/05 职场文书
创业不要错过,这4种餐饮新模式
2019/07/18 职场文书
Python获取字典中某个key的value
2022/04/13 Python
golang使用map实现去除重复数组
2022/04/14 Golang
Mysql查询时间区间日期列表,不会由于数据表数据影响
2022/04/19 MySQL