Npm 是目前最大的 JavaScript 模块仓库,里面有来自全世界开发者上传的可复用模块。 虽然大多数情况下你都是这些开放模块的使用者,但有时候你也许会成为贡献者,开发一个模块上传到 Npm 仓库。
发布到 Npm 仓库的模块有以下几个特点:
package.json
文件。该文件描述了模块的入口文件是哪个,该模块又依赖哪些模块等。想深入了解可以阅读文章 package.json文件。Webpack 不仅可用于构建运行的应用,也可用于构建上传到 Npm 的模块。 接下来用教大家如何用 Webpack 构建一个可上传的 Npm 仓库的 React 组件,具体要求如下:
在开始前先看下最终发布到 Npm 仓库的模块的目录结构:
node_modules/hello-webpack
├── lib
│ ├── index.css (组件所有依赖的 CSS 都在这个文件中)
│ ├── index.css.map
│ ├── index.js (符合 CommonJS 模块化规范的 ES5 代码)
│ └── index.js.map
├── src (ES6 源码)
│ ├── index.css
│ └── index.js
└── package.json (模块描述文件)
由于本节的重点不在于 React 而在于 Webpack,所以写一个最简单的 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>
}
}
要使用该模块时只需要这样:
// 通过 ES6 语法导入
import HelloWebpack from 'hello-webpack';
import 'hello-webpack/lib/index.css';
// 或者通过 ES5 语法导入
var HelloWebpack = require('hello-webpack');
require('hello-webpack/lib/index.css');
// 使用 react-dom 渲染
render(<HelloWebpack/>);
接下来用 Webpack 一条条来解决上面抛出问题的4点要求。
devtool: 'source-map'
输出 Source Map 以发布调试。output.libraryTarget='commonjs2'
使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用。在2-2 Output-libraryTarget 和 library 有介绍这个配置的含义。相关的 Webpack 配置代码如下:
module.exports = {
output: {
// 输出的代码符合 CommonJS 模块化规范,以供给其它模块导入使用。
libraryTarget: 'commonjs2',
},
// 输出 Source Map
devtool: 'source-map',
};
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module: {
rules: [
{
// 增加对 CSS 文件的支持
test: /\.css/,
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['css-loader']
}),
},
]
},
plugins: [
new ExtractTextPlugin({
// 输出的 CSS 文件名称
filename: 'index.css',
}),
],
};
此步引入了3个新依赖:
# 安装 Webpack 构建所需要的新依赖
npm i -D style-loader css-loader extract-text-webpack-plugin
例如下面这段 ES6 代码
class HelloWebpack extends Component{
}
在被转换成能正常运行的 ES5 代码时需要以下2个辅助函数:
babel-runtime/helpers/createClass
用于实现 class 语法babel-runtime/helpers/inherits
用于实现 extends 语法默认的情况下 Babel 会在每个输出文件中内嵌这些依赖的辅助函数的代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会重复的出现很多次,造成代码冗余。
为了不让这些辅助函数的代重复出现,可以在依赖它们的时候通过 require('babel-runtime/helpers/createClass')
的方式去导入,这样就能做到只让它们出现一次。
babel-plugin-transform-runtime 插件就是用来做这个事情的。
修改 .babelrc
文件,为其加入 transform-runtime 插件:
{
"plugins": [
[
"transform-runtime",
{
// transform-runtime 默认会自动的为你使用的 ES6 API 注入 polyfill
// 假如你在源码中使用了 Promise,输出的代码将会自动注入 require('babel-runtime/core-js/Promise') 语句
// polyfill 的注入应该交给模块使用者,因为使用者可能在其它地方已经注入的其它的 Promise polyfill 库
// 所以关闭该功能
"polyfill": false
}
]
]
}
由于加入 babel-plugin-transform-runtime 后生成的代码中会大量出现类似 require('babel-runtime/helpers/createClass')
这样的语句,所以输出的代码将依赖 babel-runtime
模块。
此步引入了3个新依赖:
# 安装 Webpack 构建所需要的新依赖
npm i -D babel-plugin-transform-runtime
# 安装输出代码运行时所需的新依赖
npm i -S babel-runtime
Externals 用来告诉 Webpack 要构建的代码中使用了哪些不用被打包的模块,也就是说这些模版是外部环境提供的,Webpack 在打包时可以忽略它们。
相关的 Webpack 配置代码如下:
module.exports = {
// 通过正则命中所有以 react 或者 babel-runtime 开头的模块
// 这些模块使用外部的,不能被打包进输出的代码里
externals: /^(react|babel-runtime)/,
};
完成以上4步后最终的 Webpack 完整配置代码如下:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// 模块的入口文件
entry: './src/index.js',
output: {
// 输出文件的名称
filename: 'index.js',
// 输出文件的存放目录
path: path.resolve(__dirname, 'lib'),
// 输出的代码符合 CommonJS 模块化规范,以供给其它模块导入使用。
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: ExtractTextPlugin.extract({
use: ['css-loader']
}),
},
]
},
plugins: [
new ExtractTextPlugin({
// 输出的 CSS 文件名称
filename: 'index.css',
}),
],
// 输出 Source Map
devtool: 'source-map',
};
重新执行构建后,你将会在项目目录下看到一个新目录 lib
,里面放着要发布到 Npm 仓库的最终代码。
在把构建出的代码发布到 Npm 仓库前,还需要确保你的模块描述文件 package.json
是正确配置的。
由于构建出的代码的入口文件是 ./lib/index.js
,需要修改 package.json
中的 main
字段如下:
{
"main": "lib/index.js",
"jsnext:main": "src/index.js"
}
其中 jsnext:main
字段用于指出采用 ES6 编写的模块入口文件所在的位置。
修改完毕后在项目目录下执行 npm publish
就能把构建出的代码发布到 Npm 仓库中(确保已经 npm login
过)。
本实例提供项目完整代码