前面的话
我们平时都是使用npm仓库中别人的模块,如果我们想自己创建模块并且发布到Npm仓库供别人使用,应该如何操作呢?
Npm仓库的模块特点
- 在每个模块根目录下都应该必须有一个描述该模块的package.json文件。
- 模块中的文件已JavaScript文件为主,但不限于JavaScript文件,也可以同时有JavaScript、css、图片文件
- 模块最好遵守CommonJs模块化规范(目前支持比较广泛)
实例
webpack不仅可以构建可运行的应用,也可以构建可上传到npm的模块。下面通过一个实例来具体说明:
编写一个可上传到Npm仓库的React组件,要求如下:
- 源码采用es6编写,但发布到Npm仓库时需要转为es5,并且遵守CommonJS模块化规范,提供Source Map以方便调试
- 该组件依赖的其他资源,如css文件也应该包含在其中
- 减少代码的冗余,减少发布出去的代码大小
- 发布出去的组件的代码中不含其他依赖的模块的代码
简单的React组件
第一步:写一个简单的React组件,源码都放在src目录下:
index.js文件:
import React, {Component} from 'react';
import './index.css';
// 导出该组件以供其他模块使用
export default class HelloWebpack extends Component {
render() {
return <h1 className="hello-component">Hello,Webpack</h1>
}
}
index.css文件:
.hello-component {
color: red;
background: black;
}
使用webpack构建Npm模块
依照上面的4点要求,给出解决方法:
-
要求一: 使用babel-loader将es6代码转换为es5代码; 配置.babelrc文件; 开启devtool:‘source-map’; 设置output.libraryTarget: ‘commonjs2’,使代码符合CommonJS2模块化规范。
webpack相关配置如下:
module.exports = { output:{ // 以commonjs2规范导出,以供其他模块使用 libraryTarget: 'commonjs2' }, module: { rules: [ { test: /\.js$/, use: ['babel-loader'], // 排除 node_modules 目录下的文件 // node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换 exclude: path.resolve(__dirname, 'node_modules'), }] } // 输出Source Map devtool: 'source-map' }
.babelrc文件相关配置如下:
{ "presets": [ "env", "react" ], }
-
要求二: 将css文件打包,通过css-loader,与mini-css-extract-plugin实现。
webpack相关配置如下:
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 在module的rules中添加 module: { rules: [ { test: /\.js$/, // 省略 }, { // 增加对css的支持 test: /\.css/, // 提取出Chunk中的css代码到单独的文件 use: [ MiniCssExtractPlugin.loader, 'css-loader' ] }] }, plugins: [ new MiniCssExtractPlugin({ filename: 'index.css' }) ],
-
要求3:在es6转es5时代码会注入一些辅助函数,如果多个文件都依赖这些辅助函数,那么这些辅助函数的代码会重复出现多次,为减少代码冗余,可以使用babel-plugin-transform-runtime插件来使这些辅助函数只出现一次。
注意:babel-plugin-transform-runtime插件要配合使用babel-runtime模块使用。 修改.babelrc文件:
"plugins": [ [ "transform-runtime", { /*transform-runtime 默认会自动为我们使用es6 API注入polyfill , 假如我们在源码中使用了Promise,则输出的代码将会自动注入require('babel-runtime/core-js/Promise')语句, polyfill的注入应该交给模块的使用者,因为使用者可能在其他地方注入了其他Promise Polyfill库,所以关闭这个功能 */ "polyfill": false // 加入babel-plugin-transform-runtime后,需要与babel-runtime一起配合使用 } ] ]
-
要求4: 使用externals来告诉webpack哪些模块时外部环境提供的,不需要被打包,时webpack在打包时将它们忽略。
webpack相关配置:
module.exports = { // 通过正则命中所有以react或者babel-runtime开头的模块,这些模块通过注册在运行环境中的全局变量访问,不用 // 被重复打包进输出的代码里 externals: /^(react|babel-runtime})/, }
完整的webpack配置文件
上面根据要求分别给出了解决的方法,下面是webpack配置文件的完整代码:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'index.js',
// 输出文件存放目录
path: path.resolve(__dirname, 'lib'),
// 以何种方式导出库
libraryTarget: 'commonjs2'
},
// 通过正则命中所有以react或者babel-runtime开头的模块,这些模块通过注册在运行环境中的全局变量访问,不用
// 被重复打包进输出的代码里
externals: /^(react|babel-runtime})/,
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
// 排除 node_modules 目录下的文件
// node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// 增加对css的支持
test: /\.css/,
// 提取出Chunk中的css代码到单独的文件
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'index.css'
})
],
// 输出Source Map
devtool: 'source-map'
}
需要引入的依赖如下:
执行构建
执行构建之后,我们在项目目录下会看到新目录lib,里面放着要发布到Npm仓库的最终代码。
发布到Npm
将构建的代码发布到Npm仓库前,我们需要修改package.json文件:
由于构建出的代码的入口文件上是:./lib/index.js,所以我们要修改package.json中的main字段:
// 构建出的代码入口文件是:"./lib/index.js",将代码发到Npm仓库前,需要修改main字段
"main": "./src/index.js",
// jsnext:main字段用于指出ES6编写的模块入口所在的位置
"jsnext:main": "src/index.js",
修改完毕后,执行npm login之后,再执行npm publish ,就能将构建出的代码发布到Npm仓库中。之后就可以自行下载这个模块,放在其他应用中使用。
完整项目地址:构建Npm模块