本文涉及包版本:node 11.6.0 、npm 6.11.3汰瘫、webpack 4.39.3狂打;使用mac開發(fā);
項目
源碼 -> https://github.com/jiaoyanlin/npm-library-demo混弥,求star??
npm i
npm start
建議開始動手實踐前先瀏覽下本文的的 三趴乡、知識點
一、發(fā)布包基本流程
1蝗拿、使用nrm管理npm源:
nrm:npm registry 管理工具晾捏,方便切換不同的源;我們開發(fā)的包要發(fā)布的源是https://registry.npmjs.org哀托,更詳細的安裝可以參考nrm —— 快速切換 NPM 源
// 安裝
npm install -g nrm
// 查看
nrm ls
// 切換
nrm use taobao
// 增加源
nrm add <registry> <url> [home]
// 刪除源
nrm del <registry>
2惦辛、發(fā)布包:
記得先在 https://www.npmjs.com 注冊賬戶并在郵箱激活賬戶
(1)編寫包代碼(npm init等操作,具體在下面會提及)
(2)切換registry到npm對應鏈接https://registry.npmjs.org/:nrm use npm
(3)登錄:npm login
(4)發(fā)布仓手、更新:npm publish
3胖齐、關(guān)于為何選擇rollup而不是webpack編寫一個npm包
為了支持tree shaking,得導出一份符合es6模塊規(guī)范的代碼嗽冒,但是webpack不支持導出為es6模塊呀伙,所以使用rollup來開發(fā)我們的包
rollup和webpack使用場景分析中提到:Rollup偏向應用于js庫,webpack偏向應用于前端工程添坊,UI庫剿另;如果你的應用場景中只是js代碼,希望做ES轉(zhuǎn)換,模塊解析驰弄,可以使用Rollup麻汰。如果你的場景中涉及到css、html戚篙,涉及到復雜的代碼拆分合并五鲫,建議使用webpack。
rollup可以直接構(gòu)建出符合es6模塊規(guī)范的代碼(有利于tree shaking)岔擂,但是webpack不能位喂;因此為了更好地使用es6模塊化來實現(xiàn)tree shaking,以及優(yōu)化包代碼體積等原因乱灵,選用rollup來開發(fā)npm包塑崖;
二、使用rollup構(gòu)建npm包
以下內(nèi)容引自rollup中文網(wǎng): 為了確保你的 ES6 模塊可以直接與「運行在 CommonJS(例如 Node.js 和 webpack)中的工具(tool)」使用痛倚,你可以使用 Rollup 編譯為 UMD 或 CommonJS 格式规婆,然后在 package.json 文件的 main 屬性中指向當前編譯的版本。如果你的 package.json 也具有 module 字段蝉稳,像 Rollup 和 webpack 2 這樣的 ES6 感知工具(ES6-aware tools)將會直接導入 ES6 模塊版本抒蚜。
關(guān)于rollup更加詳細的介紹及使用,可以參考以下文章:Rollup:下一代ES模塊打包工具 耘戚、rollup中文網(wǎng) 嗡髓、Rollup.js 實戰(zhàn)學習筆記、webpack創(chuàng)建library
1收津、先來個簡單的demo:源碼
(1)新建一個文件夾npm-library-demo
初始化:
cd npm-library-demo
npm init -y // 初始化饿这,生成package.json
npm i rollup -D // 安裝rollup
根據(jù)以下目錄結(jié)構(gòu)新增文件夾及文件:
npm-library-demo
|--build
|--rollup.config.js
|--example
|--index.html
|--src
|--main.js
|--foo.js
(2)文件內(nèi)容:
package.json中加入構(gòu)建腳本命令:
"scripts": {
"build": "rollup -c ./build/rollup.config.js"
}
// rollup.config.js
const path = require('path');
const resolve = function (filePath) {
return path.join(__dirname, '..', filePath)
}
export default {
input: resolve('src/main.js'), // 入口文件
output: { // 出口文件
file: resolve('dist/bundle.js'),
format: 'umd',
name: 'myLibrary'
}
};
// main.js
import foo from './foo.js';
export default (function () {
console.log(foo);
})();
// foo.js
export default 'hello world!';
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>example</title>
</head>
<body>
</body>
<script src="../dist/bundle.js"></script>
</html>
(3)執(zhí)行npm run build
,就可以生成打包文件/dist/bundle.js撞秋,打開example/index.html控制臺可以查看打包文件是否生效
2长捧、使用插件,在1的基礎(chǔ)上進行以下操作:源碼
在rollup中如果要處理json部服,就要用到插件唆姐,比如rollup-plugin-json
npm i rollup-plugin-json -D
// rollup.config.js
const path = require('path');
import json from 'rollup-plugin-json';
const resolve = function (filePath) {
return path.join(__dirname, '..', filePath)
}
export default {
input: resolve('src/main.js'),
output: {
file: resolve('dist/bundle.js'),
format: 'umd',
name: 'myLibrary'
},
plugins: [ // 在此處使用插件
json(),
],
};
// main.js
import foo from './foo.js';
import { version } from '../package.json'; // 利用json插件可以獲得package.json中的數(shù)據(jù)
console.log('version ' + version);
export default (function () {
console.log(foo);
})();
此時再次使用npm run build
打包,打開index.html廓八,在控制臺可以看到相關(guān)結(jié)果
其他插件使用方式類似
3奉芦、Rollup 與其他工具集成
(1)npm packages:添加配置讓rollup知道如何處理你從npm安裝到node_modules文件夾中的軟件包
npm install rollup-plugin-node-resolve rollup-plugin-commonjs -D
- rollup-plugin-node-resolve: 告訴 Rollup 如何查找外部模塊
- rollup-plugin-commonjs:將CommonJS模塊轉(zhuǎn)換為 ES2015 供 Rollup 處理,請注意剧蹂,rollup-plugin-commonjs應該用在其他插件轉(zhuǎn)換你的模塊之前 - 這是為了防止其他插件的改變破壞CommonJS的檢測
// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json';
const path = require('path');
const resolveFile = function (filePath) {
return path.join(__dirname, '..', filePath)
}
export default {
input: resolveFile('src/main.js'),
output: {
file: resolveFile('dist/bundle.js'),
format: 'cjs',
},
plugins: [
commonjs(),
resolve({
// 將自定義選項傳遞給解析插件
customResolveOptions: {
moduleDirectory: 'node_modules'
}
}),
json(),
],
};
(2)external:有些包要處理成外部引用(例如lodash等)声功,externals就是用來處理外部的引用,不要將這些包打包到輸出文件中宠叼,減小打包文件體積
external 接受一個模塊名稱的數(shù)組或一個接受模塊名稱的函數(shù)先巴,如果它被視為外部引用(externals)則返回true
// rollup.config.js
export default {
...,
// 作用:指出應將哪些模塊視為外部模塊其爵,否則會被打包進最終的代碼里
external: ['lodash']
// external: id => /lodash/.test(id) // 也可以使用這種方式
};
安裝lodash:npm i lodash -S
// main.js
...
import _ from 'lodash';
console.log('-------lodash:', _.defaults({ 'a': 1 }, { 'a': 3, 'b': 2 }));
可以打包試試external配置與否對打包文件的影響(直接查看dist/bundle.js)
由于此時打包生成的是cjs格式的js,可以直接在控制臺執(zhí)行node ./dist/bundle.js
測試打包結(jié)果伸蚯;此時index.html是沒法成功加載bundle.js的摩渺,因為此時的文件是cjs的,無法直接在瀏覽器中使用
(3)babel7:
npm i -D rollup-plugin-babel @babel/core @babel/plugin-transform-runtime @babel/preset-env
npm i -S @babel/runtime @babel/runtime-corejs2
// rollup.config.js
...
import babel from 'rollup-plugin-babel';
export default {
...
plugins: [
...,
babel({
exclude: 'node_modules/**', // 只編譯我們的源代碼
runtimeHelpers: true,
}),
],
external: id => {
return /@babel\/runtime/.test(id) || /lodash/.test(id);
}
}
根目錄下新建文件.babelrc.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
// "debug": true, // debug剂邮,編譯的時候 console
"useBuiltIns": false, // 是否開啟自動支持 polyfill
"modules": false, // 模塊使用 es modules 摇幻,不使用 commonJS 規(guī)范
// "targets": "> 0.25%, last 2 versions, iOS >= 8, Android >= 4.4, not dead"
}
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
// useESModules:引入的helpers是否是es modules規(guī)范的;注意當打包成cjs時不能引入es modules下的代碼挥萌,會報錯
// "useESModules": true,
"corejs": 2 // 參考官方文檔
}
],
]
}
可以自己在main.js中加入一些es6語法绰姻,看看打包后的文件是否將es6語法編譯成了es5(如const、let等)
??????? babel還有一篇相關(guān)博文補充
(4)引入eslint:
npm i -D babel-eslint rollup-plugin-eslint
eslint位置很重要引瀑,放在babel插件后面會導致定位問題的時候出錯
// rollup.config.js
...
import { eslint } from 'rollup-plugin-eslint';
module.exports = {
...,
plugins: [
...,
eslint({ // eslint插件必須放在babel插件之前狂芋,不然檢測的是轉(zhuǎn)換后的文件,導致檢測有誤
throwOnError: true,
throwOnWarning: true,
include: ['src/**'],
exclude: ['node_modules/**']
}),
...
]
}
根目錄下新增文件.eslitrc.js
module.exports = {
//一旦配置了root憨栽,ESlint停止在父級目錄中查找配置文件
root: true,
parser: "babel-eslint", // 配置babel-eslint帜矾,避免在使用es6類屬性時,eslint報Parsing error: Unexpected token
//想要支持的JS語言選項
parserOptions: {
//啟用ES6語法支持(如果支持es6的全局變量{env: {es6: true}}徒像,則默認啟用ES6語法支持)
//此處也可以使用年份命名的版本號:2015
ecmaVersion: 6,
//默認為script
sourceType: "module",
//支持其他的語言特性
ecmaFeatures: {}
},
//代碼運行的環(huán)境黍特,每個環(huán)境都會有一套預定義的全局對象,不同環(huán)境可以組合使用
env: {
amd: true, // 否則會出現(xiàn)'require' is not defined 提示
es6: true,
browser: true,
jquery: true
},
//訪問當前源文件中未定義的變量時锯蛀,no-undef會報警告。
//如果這些全局變量是合規(guī)的次慢,可以在globals中配置旁涤,避免這些全局變量發(fā)出警告
globals: {
//配置給全局變量的布爾值,是用來控制該全局變量是否允許被重寫
test_param: true,
window: true,
process: false,
},
//集成推薦的規(guī)則
extends: ["eslint:recommended"],
//啟用額外的規(guī)則或者覆蓋默認的規(guī)則
//規(guī)則級別分別:為"off"(0)關(guān)閉迫像、"warn"(1)警告劈愚、"error"(2)錯誤--error觸發(fā)時,程序退出
rules: {
//關(guān)閉“禁用console”規(guī)則
"no-console": "off",
//縮進不規(guī)范警告闻妓,要求縮進為2個空格菌羽,默認值為4個空格
"indent": ["warn", 4, {
//設置為1時強制switch語句中case的縮進為2個空格
"SwitchCase": 1,
}],
// 函數(shù)定義時括號前面要不要有空格
"space-before-function-paren": [0, "always"],
//定義字符串不規(guī)范錯誤,要求字符串使用雙引號
// quotes: ["error", "double"],
//....
//更多規(guī)則可查看http://eslint.cn/docs/rules/
}
}
(5)一次編譯由缆,同時打包生成不同格式文件注祖,如cjs、es均唉、umd等
有兩種方法:
首先是晨,npm i -D rollup-plugin-serve rollup-plugin-uglify
修改packag.json
{
...,
"module": "es/index.js",
"main": "lib/index.js",
"scripts": {
"build": "rollup -c ./build/rollup.config.js"
"clean": "rm -rf ./dist/ ./es/ ./lib/",
"easy": "npm run clean && NODE_ENV=development rollup -w -c ./build/easy.config.js",
"node:dev": "npm run clean && NODE_ENV=development node ./build/dev.js",
"node:build": "npm run clean && NODE_ENV=production node ./build/build.js",
"start": "npm run clean && NODE_ENV=development rollup -w -c ./build/rollup.config.js",
"build": "npm run clean && NODE_ENV=production rollup -c ./build/rollup.config.js"
},
"files": [
"dist",
"lib",
"es",
"types"
],
注意:mac可以直接使用NODE_ENV=development方式傳遞變量,window下不一定可以舔箭,如果失敗請引入cross-env
(1)第一種方法:使用rollup命令打包
// rollup.config.js
import json from 'rollup-plugin-json';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babelPlugin from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
import { uglify } from 'rollup-plugin-uglify';
import { eslint } from 'rollup-plugin-eslint'
const path = require('path');
const resolveFile = function (filePath) {
return path.join(__dirname, '..', filePath)
}
const isDev = process.env.NODE_ENV !== 'production';
console.log('----------dev:', process.env.NODE_ENV, isDev)
// 通過控制outputs中對應的isExternal罩缴、isUglify值來決定打包的文件是否啟用external和uglify
const outputs = [
{
file: resolveFile('lib/index.js'),
format: 'cjs',
isExternal: true,
},
{
file: resolveFile('es/index.js'),
format: 'es',
isExternal: true,
},
{
file: resolveFile('dist/index.js'),
format: 'umd',
name: 'npmLibraryDemo',
},
{
file: resolveFile('dist/index.min.js'),
format: 'umd',
name: 'npmLibraryDemo',
isUglify: true,
}
].map(i => {
i.sourcemap = isDev; // 開發(fā)模式:開啟sourcemap文件的生成
return i;
});
const len = outputs.length;
const config = outputs.map((output, i) => {
const isUglify = output.isUglify || false;
const isExternal = output.isExternal || false;
console.log('------config:', isExternal)
return {
input: resolveFile('src/main.js'),
output,
plugins: [
// rollup-plugin-commonjs應該用在其他插件轉(zhuǎn)換你的模塊之前 - 這是為了防止其他插件的改變破壞CommonJS的檢測
// 作用:將CommonJS模塊轉(zhuǎn)換為 ES2015 供 Rollup 處理
commonjs(),
// 作用:處理json格式文件
json(),
// 作用:告訴 Rollup 如何查找外部模塊
resolve({
// 將自定義選項傳遞給解析插件
customResolveOptions: {
moduleDirectory: 'node_modules'
}
}),
eslint({
throwOnError: true,
throwOnWarning: true,
include: ['src/**'],
exclude: ['node_modules/**']
}),
babelPlugin({
exclude: 'node_modules/**', // 只編譯我們的源代碼
runtimeHelpers: true,
}),
...(
isDev && i === len - 1 ?
[
serve({ // 使用開發(fā)服務插件
port: 3001,
// 設置 exmaple的訪問目錄和dist的訪問目錄
contentBase: [resolveFile('example'), resolveFile('dist')]
})
] : isUglify ? [
uglify()
] : []
)
],
// 作用:指出應將哪些模塊視為外部模塊,否則會被打包進最終的代碼里
external: id => {
return !isExternal ? false :
(/@babel\/runtime/.test(id) || /lodash/.test(id));
}
}
})
export default config;
使用npm start
開啟開發(fā)模式;使用npm run build
可以打包出文件箫章;總共導出三種格式文件:cjs烙荷、es、umd檬寂,umd格式的文件有壓縮和未壓縮
start時如果報錯“getaddrinfo ENOTFOUND localhost”奢讨,參考這篇方法解決
(2)第二種方法:使用rollup api進行打包
build文件夾下新增文件:node.config.js、dev.js焰薄、build.js
// node.config.js
const json = require('rollup-plugin-json');
const resolve = require('rollup-plugin-node-resolve');
const commonjs = require('rollup-plugin-commonjs');
const babelPlugin = require('rollup-plugin-babel');
const { uglify } = require('rollup-plugin-uglify');
const path = require('path');
const isDev = process.env.NODE_ENV !== 'production';
const resolveFile = function (filePath) {
return path.join(__dirname, '..', filePath)
}
module.exports.outputs = [
{
file: resolveFile('lib/index.js'),
format: 'cjs',
isExternal: true,
},
{
file: resolveFile('es/index.js'),
format: 'es',
isExternal: true,
},
{
file: resolveFile('dist/index.js'),
format: 'umd',
name: 'npmLibraryDemo',
},
{
file: resolveFile('dist/index.min.js'),
format: 'umd',
name: 'npmLibraryDemo',
isUglify: true,
}
].map(i => {
i.sourcemap = isDev; // 開發(fā)模式:開啟sourcemap文件的生成
return i;
});
module.exports.configFun = function config({isUglify, isExternal} = {}) {
return {
input: resolveFile('src/main.js'),
plugins: [
// rollup-plugin-commonjs應該用在其他插件轉(zhuǎn)換你的模塊之前 - 這是為了防止其他插件的改變破壞CommonJS的檢測
// 作用:將CommonJS模塊轉(zhuǎn)換為 ES2015 供 Rollup 處理
commonjs(),
// 作用:處理json格式文件
json(),
// 作用:告訴 Rollup 如何查找外部模塊
resolve({
// 將自定義選項傳遞給解析插件
customResolveOptions: {
moduleDirectory: 'node_modules'
}
}),
babelPlugin({
exclude: 'node_modules/**', // 只編譯我們的源代碼
runtimeHelpers: true,
}),
...(
isUglify ? [ uglify() ] : []
)
],
// 作用:指出應將哪些模塊視為外部模塊拿诸,否則會被打包進最終的代碼里
external: id => {
return !isExternal ? false :
(/@babel\/runtime/.test(id) || /lodash/.test(id));
},
}
};
// dev.js
const path = require('path');
const serve = require('rollup-plugin-serve');
const rollup = require('rollup');
const { configFun, outputs } = require('./node.config.js');
const resolveFile = function (filePath) {
return path.join(__dirname, '..', filePath)
}
let watchOptions = [];
const len = outputs.length;
outputs.forEach((output, i) => {
let options = {
isUglify: output.isUglify,
isExternal: output.isExternal,
}
let config = {
output,
...configFun(options)
};
if (i === len - 1) {
config.plugins.push(
serve({ // 使用開發(fā)服務插件
port: 3001,
// 設置 exmaple的訪問目錄和dist的訪問目錄
contentBase: [resolveFile('example'), resolveFile('dist')]
})
);
}
watchOptions.push(config);
});
const watcher = rollup.watch(watchOptions);
watcher.on('event', event => {
// event.code 會是下面其中一個:
// START — 監(jiān)聽器正在啟動(重啟)
// BUNDLE_START — 構(gòu)建單個文件束
// BUNDLE_END — 完成文件束構(gòu)建
// END — 完成所有文件束構(gòu)建
// ERROR — 構(gòu)建時遇到錯誤
// FATAL — 遇到無可修復的錯誤
switch (event.code) {
case 'START':
console.log(`[info] 監(jiān)聽器正在啟動(重啟)`);
break;
case 'BUNDLE_START':
console.log(`[info] 開始構(gòu)建 ${event.output}`);
break;
case 'BUNDLE_END':
console.log(`[info] 完成構(gòu)建 ${event.output}`);
console.log(`[info] 構(gòu)建時長 ${event.duration}`);
break;
case 'END':
console.log(`[info] 完成所有構(gòu)建`);
break;
case 'ERROR':
case 'FATAL':
console.log(`[error] 構(gòu)建發(fā)生錯誤`);
}
});
// 停止監(jiān)聽
// watcher.close();
// build.js
const rollup = require('rollup');
const { configFun, outputs } = require('./node.config.js');
outputs.forEach(async (output) => {
const inputOptions = configFun({
isUglify: output.isUglify,
isExternal: output.isExternal,
});
build(inputOptions, output);
})
async function build(inputOptions, outputOptions) {
console.log(`[INFO] 開始編譯 ${inputOptions.input}`);
// create a bundle
const bundle = await rollup.rollup(inputOptions);
// generate code and a sourcemap
const res = await bundle.generate(outputOptions);
console.log(`[INFO] ${res}`);
// or write the bundle to disk
await bundle.write(outputOptions);
console.log(`[SUCCESS] 編譯結(jié)束 ${outputOptions.file}`);
}
使用npm run node:dev
開啟開發(fā)模式;使用npm run node:build
可以打包出文件塞茅;
可以實現(xiàn)一個簡單功能實驗一下配置是否成功亩码,比如這次提交簡單預加載圖片
注意:
1、examp/index.html中引入的js是umd形式的野瘦,如果我們的代碼中引入了運行時需要使用到的第三方包(例如lodash等)忠烛,并且沒有在index.html手動將該包引入钞脂,會導致找不到該包而報錯;因此我這里的配置中,輸出文件如果是umd格式的儒旬,就不配置external,直接將第三方包的代碼一起打包進最終的打包文件中展懈;
2直砂、當輸出文件是cjs或者es時,配置external汹买,即不將某些第三方包打包佩伤,減小最終的打包文件體積;由于我們把第三方包安裝在“dependencies”中晦毙,當別人加載我們的這個包時生巡,他們的項目會自動安裝我們的“dependencies”中所有的包,所以可以加載到我們開發(fā)的包中涉及到的第三方包见妒;
3孤荣、關(guān)于調(diào)試
我開發(fā)包的過程中用到了兩種調(diào)試方式:
方法1:直接通過npm start啟動時的http://localhost:3001來調(diào)試;由于開啟了rollup的監(jiān)聽功能须揣,因此當我們修改代碼時盐股,會自動構(gòu)建打包出新代碼,只要刷新瀏覽器就能看到最新的效果返敬;
開啟source map調(diào)試我只在方法1的調(diào)試方法中能正常使用
方法2:在項目中調(diào)試正在開發(fā)的包:
npm link命令通過鏈接目錄和可執(zhí)行文件遂庄,實現(xiàn)任意位置的npm包命令的全局可執(zhí)行。
在包目錄下執(zhí)行npm link
(假設包名為pky-test)劲赠;
在項目目錄下執(zhí)行npm link pky-test
即可使用該包(執(zhí)行npm unlink pky-test
可以刪除包鏈接)涛目;
在包目錄下執(zhí)行npm start
可以實時打包出最新代碼
6秸谢、發(fā)布,增加命令實現(xiàn)自動打標簽并根據(jù)提交記錄生成changelog
npm i -D conventional-changelog-cli
package.json
{
...,
"scripts": {
...,
"tag": "node ./build/version.js",
"x": "npm --no-git-tag-version version major",
"y": "npm --no-git-tag-version version minor",
"z": "npm --no-git-tag-version version patch",
"postversion": "npm run changelog && git add . && npm run tag",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"prepublishOnly": "npm run build",
"postpublish": "npm run clean"
}
}
新建文件build/version.js:根據(jù)packag.json中的version提交代碼并且打標簽
const fs = require('fs');
const path = require('path');
const pathname = path.resolve(__dirname, '../package.json');
const pkg = JSON.parse(fs.readFileSync(pathname, 'utf-8'));
let version = pkg.version;
console.log('version:', version)
const exec = require('child_process').exec;
let cmdStr = `git commit -m "v${version}" && git push && git tag -a "v${version}" -m "${version}" && git push origin --tags`;
exec(cmdStr, function (err, stdout, stderr) {
console.log('exec:', err, stdout, stderr);
});
(1)執(zhí)行npm run x/y/z
可以改變package.json中的version霹肝,然后根據(jù)提交的commit信息自動生成changelog估蹄,最后會根據(jù)version提交代碼并打標簽;
(2)執(zhí)行npm run publish
發(fā)布代碼
其他:
(1)我沒有使用preversion鉤子和
conventional-changelog-cli自動生成changelog沫换,因為如果在改變版本號之前執(zhí)行自動生成changelog臭蚁,那么當前版本提交的commit信息不會被自動生成到changelog中(因為changelog只會生成當前版本之前的commit記錄)
(2)必須遵循一定的commit規(guī)范,才能根據(jù)commit記錄自動生成changelog讯赏,具體自行百度下conventional-changelog-cli的使用哦
因此垮兑,推薦的工作流:
1.改動代碼
2.提交這些改動
3.改變package.json中的版本號
4.使用conventional-changelog工具
5.提交 package.json和CHANGELOG.md文件
6.打標簽tag
7.push代碼
可以參考使用conventional-changelog生成版本日志
三、知識點
先學習下以下兩篇文章:
如何開發(fā)和維護一個npm項目
你所需要的npm知識儲備都在這了
1漱挎、package.json中需要注意的點:
(1)version:
版本格式: [主版本號major.次版本號minor.修訂號patch]
先行版本: 內(nèi)部版本alpha系枪、公測版本beta、Release candiate正式版本的候選版本rc磕谅,例如1.0.0-alpha私爷、1.0.0-beta.1
使用npm version進行版本號管理:
npm version 1.0.1 # 顯示設置版本號為 1.0.1
npm version major # major + 1,其余版本號歸 0
npm version minor # minor + 1膊夹,patch 歸 0
npm version patch # patch + 1
# 預發(fā)布版本
# 當前版本號為 1.2.3
npm version prepatch # 版本號變?yōu)?1.2.4-0衬浑,也就是 1.2.4 版本的第一個預發(fā)布版本
npm version preminor # 版本號變?yōu)?1.3.0-0,也就是 1.3.0 版本的第一個預發(fā)布版本
npm version premajor # 版本號變?yōu)?2.0.0-0放刨,也就是 2.0.0 版本的第一個預發(fā)布版本
npm version prerelease # 版本號變?yōu)?2.0.0-1工秩,也就是使預發(fā)布版本號加一
# 在git環(huán)境下npm version會默認執(zhí)行g(shù)it add->git commit->git tag
npm version minor -m "feat(version): upgrade to %s" # 可自定義commit message;%s 會自動替換為新版本號
# 模塊 tag 管理
# 當前版本為1.0.1
npm version prerelease # 1.0.2-0
npm publish --tag beta # 發(fā)布包beta版本宏榕,打上beta tag
npm dist-tag ls xxx # 查看某個包的tag拓诸;beta: 1.0.2-0
npm install xxx@beta # 下載beta版本 1.0.2-0
# 當prerelease版本已經(jīng)穩(wěn)定了,可以將prerelease版本設置為穩(wěn)定版本
npm dist-tag add xxx@1.0.2-0 latest
npm dist-tag ls xxx # latest: 1.0.2-0
npm version 可以更新包版本麻昼,當倉庫已經(jīng)被git初始化了,那么運行npm version修改完版本號以后馋辈,還會運行g(shù)it add 抚芦、git commit和git tag的命令,其中commit的信息默認是自改完的版本號
(2)main迈螟、module叉抡、sideEffect:
- main、module:用來指定npm包的入口文件
- main: npm自帶答毫,一般表示符合CommonJS規(guī)范的文件入口
- module: 符合ES模塊規(guī)范的文件入口褥民,使得代碼可進行Tree Shaking;并且在webpack的默認配置中洗搂,module的優(yōu)先級要高于main
因為一般項目配置babel時消返,為了加速項目編譯過程载弄,會忽略node_modules中的模塊,所以module入口的文件最好是符合ESmodule規(guī)范的ES5的代碼(說白了就是該文件只有導入導出是用的ES6模塊化語法撵颊,其他都已經(jīng)轉(zhuǎn)成了es5)宇攻,webpack最終會把ESmodule轉(zhuǎn)換為它自己的commonjs規(guī)范的代碼
- sideEffect:webpack4中新增特性,表示npm包的代碼是否有副作用倡勇;
sideEffect可設置為Boolean或者數(shù)組;當為false時逞刷,表明這個包是沒有副作用的,可以進行按需引用;如果為數(shù)組時妻熊,數(shù)組的每一項表示的是有副作用的文件在組件庫開發(fā)的時候夸浅,如果有樣式文件,需要把樣式文件的路徑放到sideEffect的數(shù)組中扔役,因為UglifyJs只能識別js文件帆喇,如果不設置的話,最后打包的時候會把樣式文件忽略掉厅目。
由于webpack4引入了sideEffect番枚,因此當?shù)谌桨O置了sideEffect時,可以直接去除沒有用到的代碼损敷,比如antd組件庫設置sideEffect葫笼,那在webpack4時就不用再依賴babel-plugin-import進行按需加載了,webpack打包時直接就能把沒用到的代碼通過tree-shaking清除掉拗馒。
參考文章:
package.json 中的 Module 字段是干嘛的
聊聊 package.json 文件中的 module 字段
(3)tree shaking路星,用來剔除 JavaScript 中用不上的死代碼
更多詳情可參考使用 Tree Shaking
要讓 Tree Shaking 正常工作的前提是交給 Webpack 的 JavaScript 代碼必須是采用 ES6 模塊化語法的。 因為 ES6 模塊化語法是靜態(tài)的(導入導出語句中的路徑必須是靜態(tài)的字符串诱桂,而且不能放入其它代碼塊中)洋丐,這讓 Webpack 可以簡單的分析出哪些 export 的被 import 過了。 如果你采用 ES5 中的模塊化挥等,例如 module.export={...}友绝、 require(x+y)、 if(x){require('./util')}肝劲,Webpack 無法分析出哪些代碼可以剔除迁客。
基于以上說明,需要做一些配置讓tree shaking生效:
第一種情況--針對項目:
- 把采用 ES6 模塊化的代碼直接交給 Webpack辞槐,需要配置 Babel 讓其保留 ES6 模塊化語句掷漱,修改 .babelrc 文件如下;要剔除用不上的代碼還得經(jīng)過 UglifyJS 去處理一遍榄檬,因此需要在項目中引入UglifyJSPlugin卜范;
{
"presets": [
[
"env",
{
"modules": false
}
]
]
}
- 在package.json中根據(jù)實際情況設置sideEffects,詳細解釋請看上面的第(2)點
第二種情況--針對npm包開發(fā):
- 提供兩份代碼鹿榜,一份采用 CommonJS 模塊化語法海雪,一份采用 ES6 模塊化語法锦爵,package.json 文件中有兩個字段:
{
"main": "lib/index.js", // 指明采用 CommonJS 模塊化的代碼入口
"module": "es/index.js" // 指明采用 ES6 模塊化的代碼入口;當該代碼存在時喳魏,webpack會優(yōu)先加載這個代碼
}
- 根據(jù)情況設置package.json中的sideEffects字段
關(guān)于tree shaking棉浸、sideEffects使用請查看:
Tree-Shaking性能優(yōu)化實踐 - 原理篇
你的Tree-Shaking并沒什么卵用
深入淺出 sideEffects
Webpack 中的 sideEffects 到底該怎么用
由文章可知sideEffects并不是在項目真的不存在副作用代碼時才可以設置
2、控制npm發(fā)布的包包含的文件有以下方式:
- package.json#files:數(shù)組刺彩,表示可以包含哪些文件迷郑,格式和.gitignore的寫法一樣
- .npmignore:表示哪些文件將被忽略,格式和.gitignore的寫法一樣
- .gitignore:表示要忽略哪些文件
優(yōu)先級:files > .npmignore > .gitignore
3创倔、package-lock.json:
- package-lock.json把所有依賴按照順序列出來嗡害,第一次出現(xiàn)的包名會提升到頂層,后面重復出現(xiàn)的將會放入被依賴包的node_modules當中畦攘,因此會引起不完全扁平化問題霸妹。
- 在開發(fā)應用時,建議把package-lock.json文件提交到代碼倉庫知押,從而讓團隊成員叹螟、運維部署人員或CI系統(tǒng)可以在執(zhí)行npm install時安裝的依賴版本都是一致的。
- 但在開發(fā)一個庫時台盯,則不應把package-lock.json文件提交到倉庫中罢绽。實際上,npm也默認不會把package-lock.json文件發(fā)布出去静盅。之所以這么做良价,是因為庫項目一般是被其他項目依賴的,在不寫死的情況下蒿叠,就可以復用主項目已經(jīng)加載過的包明垢,而一旦庫依賴的是精確的版本號那么可能會造成包的冗余。
4市咽、npm scripts 腳本痊银、npx、path環(huán)境變量
package.json:
"scripts": {
"serve": "vue-cli-service serve",
...
}
原理: package.json 中的 bin 字段施绎;字段 bin 表示一個可執(zhí)行文件到指定文件源的映射曼验。
例如在@vue/cli-service的package.json中:
"bin": {
"vue-cli-service": "bin/vue-cli-service.js"
}
npx:方便調(diào)用項目內(nèi)部安裝的模塊
PATH環(huán)境變量:執(zhí)行env
可查看當前所有環(huán)境變量;npm run env
可查看腳本運行時的環(huán)境變量;通過npm run可在不添加路徑前綴的情況下直接訪問當前項目node_modules/.bin目錄里面的可執(zhí)行文件
5粘姜、其他
(1)
npm outdated # 查看當前項目中可升級的模塊
npm audit [--json] # 安全漏洞檢查;加上--json熔酷,以 JSON 格式生成漏洞報告
npm audit fix # 修復存在安全漏洞的依賴包(自動更新到兼容的安全版本)
npm audit fix --force # 將依賴包版本號升級到最新的大版本孤紧,而不是兼容的安全版本;盡量避免使用--force
(2)git提交可參考以下規(guī)范:
feat:新功能(feature)
fix:修補bug
docs:文檔(documentation)
style: 格式(不影響代碼運行的變動)
refactor:重構(gòu)(即不是新增功能拒秘,也不是修改bug的代碼變動)
test:增加測試
chore:構(gòu)建過程或輔助工具的變動
(3)npm包發(fā)布流程:
(4)幾點心得:
1臭猜、對于webpack構(gòu)建的項目或者包,在babel中設置"modules": false
其實只是讓項目中經(jīng)過babel轉(zhuǎn)化后的代碼(已經(jīng)是es5)仍然保留 ES6 模塊化語句押蚤,也就是只有導入導出語句保留es6寫法蔑歌;此時webpack會自動再去轉(zhuǎn)換這里的es6模塊化語句;也就是ES6 模塊化語句交給webpack自己去轉(zhuǎn)換揽碘;
2次屠、對于webpack構(gòu)建生成的包,不支持導出為es6模塊(最終都轉(zhuǎn)成了es5雳刺,無法保留ES6 模塊化語句不轉(zhuǎn)換)劫灶,因此如果開發(fā)的npm包希望導出多種格式,推薦使用rollup
3掖桦、為了加速項目編譯過程本昏,一般都會設置忽略編譯node_modules中的模塊,所以這就需要我們開發(fā)的npm包是編譯過的枪汪;
一般來說涌穆,用于node環(huán)境的包,只要提供符合CMD規(guī)范的包雀久,但用于web的包宿稀,就要提供更多的選項:
- lib:符合commonjs規(guī)范的文件,一般放在lib這個文件夾里面岸啡,入口是mian
- es:符合ES module規(guī)范的文件原叮,一般放在es這個文件夾里面,入口是module
- dist:經(jīng)過壓縮的文件巡蘸,一般是可以通過script標簽直接引用的文件