Skip to content

Commit

Permalink
feat(module): Support dual CJS/ESM package
Browse files Browse the repository at this point in the history
* feat(module): Support dual CJS/ESM package

- add `type:”module”` to package.json
- Support for CJS/ESM by conditional exports
- for CJS, will consume packaged with d3.js to solve compatibility issue
- ESM build will distributed from ‘dist-esm’ (before dist/*.esm.js)

Fix #2202

* skip: make peer dependency on webpack 5.48
  • Loading branch information
netil committed Aug 9, 2021
1 parent 313c7c8 commit 437c007
Show file tree
Hide file tree
Showing 23 changed files with 593 additions and 428 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [11.x, 12.x, 14.x]
node-version: [12.x, 14.x, 16.x]

steps:
- uses: actions/checkout@v2
Expand All @@ -28,7 +28,7 @@ jobs:
- name: Install dependencies
run: |
npm config set package-lock false
npm install
npm install --save --legacy-peer-deps
- name: Lint
run: npm run lint
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -8,7 +8,7 @@ package-lock.json
/.*
/coverage
/demo/work
/dist
/dist*
/doc
/monitor
/node_modules
2 changes: 1 addition & 1 deletion .husky/commit-msg
@@ -1 +1 @@
npx --no-install commitlint -g ./config/commitlint.config.js --edit $1
npx --no-install commitlint -g ./config/commitlint.config.cjs --edit $1
8 changes: 4 additions & 4 deletions babel.config.js → babel.config.cjs
Expand Up @@ -24,11 +24,11 @@ module.exports = function(api) {
"@babel/plugin-transform-runtime", {
"useESModules": true
}
],
["@babel/plugin-proposal-class-properties", {
], [
"@babel/plugin-proposal-class-properties", {
"loose": true
}]
,
}
],
"@babel/proposal-object-rest-spread",
"add-module-exports",
"transform-inline-consecutive-adds",
Expand Down
6 changes: 6 additions & 0 deletions config/banner.cjs
@@ -0,0 +1,6 @@
const {resolve} = require("path");
const {execSync} = require("child_process");

module.exports = JSON.parse(
execSync(`node ${resolve(__dirname, "./banner.js")}`, {encoding: "utf-8"})
);
13 changes: 10 additions & 3 deletions config/banner.js
@@ -1,6 +1,8 @@
const pkg = require("../package.json");
import {readJson} from "./util.js";

module.exports = {
const pkg = readJson("package.json");

const banner = {
production: [
`Copyright (c) 2017 ~ present ${pkg.author}`,
`${pkg.name} project is licensed under the ${pkg.license} license`,
Expand All @@ -14,11 +16,16 @@ module.exports = {
"",
"",
`All-in-one packaged file for ease use of '${pkg.name}' with dependant d3.js modules & polyfills.`,
`- ${Object.entries(pkg.dependencies).map(v => v.join(" ")).join("\r\n- ")}`
`- ${
Object.entries(pkg.dependencies).map(v => v.join(" "))
.join("\r\n- ")
}`
].join("\r\n"),
plugin: [
"",
"@requires billboard.js",
"@summary billboard.js plugin"
].join("\r\n")
};

console.log(JSON.stringify(banner));
12 changes: 12 additions & 0 deletions config/cjs.js
@@ -0,0 +1,12 @@
/**
* Generate a CJS package.json file to dist folder.
*/
import {resolvePath, writeJson} from "./util.js";

const content = {
"type": "commonjs"
};

writeJson(resolvePath("../dist/package.json"), content, e => {
console.error(e);
});
File renamed without changes.
2 changes: 1 addition & 1 deletion config/deploy-nightly.sh
Expand Up @@ -23,7 +23,7 @@ build_nightly() {

build_and_commit() {
build_nightly
git add ./dist
git add ./dist*
git commit -a -m "skip: $VERSION build [skip ci]"
}

Expand Down
6 changes: 3 additions & 3 deletions config/deploy.sh
Expand Up @@ -45,11 +45,11 @@ build() {

# build & copy to release path
npm run build && npm run jsdoc
cp -r doc dist release/$VERSION/
cp -r doc dist* release/$VERSION/

# copy built files to dist_tag folder
mkdir release/$DIST_TAG
cp -r doc dist release/$DIST_TAG
cp -r doc dist* release/$DIST_TAG
}

push() {
Expand All @@ -62,7 +62,7 @@ push() {
fi

# push to github pages
npx gh-pages --dist $DIST_FOLDER --dest $DEST_FOLDER --add --remote $DEST_REMOTE --message $COMMIT_MESSAGE
# npx gh-pages --dist $DIST_FOLDER --dest $DEST_FOLDER --add --remote $DEST_REMOTE --message $COMMIT_MESSAGE
}

setup
Expand Down
15 changes: 8 additions & 7 deletions config/jsdoc.js
@@ -1,19 +1,20 @@
// for nightly build
const pkg = require("../package.json");
const exec = require("child_process").exec;
const fs = require("fs");
const path = require("path");
import {readJson, resolvePath} from "./util.js";
import {exec} from "child_process";
import {readFile, writeFile} from "fs";

const pkg = readJson("package.json");

exec("npm run jsdoc:cmd", () => {
// Replace version info
const file = path.resolve(__dirname, "../doc/bb.html");
const file = resolvePath("../doc/bb.html");

fs.readFile(file, "utf8", (err, data) => {
readFile(file, "utf8", (err, data) => {
if (err) throw err;

const result = data.replace(/__VERSION__/g, pkg.version);

fs.writeFile(file, result, "utf8", err => {
writeFile(file, result, "utf8", err => {
if (err) throw err;

console.log("==> API doc has been generated!");
Expand Down
26 changes: 16 additions & 10 deletions config/rollup/esm.js
@@ -1,23 +1,29 @@
import pkg from '../../package.json';
import {readdirSync} from "fs";
import babel from '@rollup/plugin-babel';
import typescript from "@rollup/plugin-typescript";
import resolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace";
import del from "rollup-plugin-delete";
import {getBanner, readJson, resolvePath} from "../util.js";

import path from "path";
import fs from "fs";
const pkg = readJson("package.json");
const distPath = "dist-esm";

const {plugin, production} = require("../banner.js");
const {plugin, production} = getBanner();
const version = process.env.VERSION || pkg.version;

function getBanner(isPlugin) {
function getBannerStr(isPlugin) {

return `/*!
* ${(production.replace(/(@version ).*$/, `$1${version}`) + (isPlugin ? plugin : "")).replace(/\r\n/gm, "\r\n * ")}
*/`;
}

const plugins = [
del({
targets: `${distPath}/*`,
runOnce: true
}),
resolve(),
babel({
babelHelpers: "runtime"
Expand All @@ -31,16 +37,16 @@ const plugins = [
const external = id => /^d3-/.test(id);

// billboard.js plugin setting
const bbPlugins = fs.readdirSync(path.resolve(__dirname, "../../src/Plugin/"), {
const bbPlugins = readdirSync(resolvePath("../../src/Plugin/"), {
withFileTypes: true
})
.filter(dirent => dirent.isDirectory())
.map(({name}) => ({
input: `src/Plugin/${name}/index.ts`,
output: {
file: `dist/esm/plugin/billboardjs-plugin-${name}.js`,
file: `${distPath}/plugin/billboardjs-plugin-${name}.js`,
format: "es",
banner: getBanner(true)
banner: getBannerStr(true)
},
plugins,
external
Expand All @@ -50,9 +56,9 @@ export default [
{
input: "src/index.esm.ts",
output: {
file: "dist/esm/billboard.js",
file: `${distPath}/billboard.js`,
format: "es",
banner: getBanner()
banner: getBannerStr()
},
plugins,
external
Expand Down
File renamed without changes.
50 changes: 50 additions & 0 deletions config/util.js
@@ -0,0 +1,50 @@
import {dirname, resolve} from "path";
import {fileURLToPath} from "url";
import {execSync} from "child_process";
import {readFileSync, writeFileSync} from "fs";

const __dirname = dirname(fileURLToPath(import.meta.url));

/**
* Get banner object
*/
function getBanner() {
return JSON.parse(
execSync(`node ${resolvePath("./banner.js")}`, {encoding: "utf-8"})
);
}

/**
* Resolve path
* @param {string} path Path to resolve
*/
function resolvePath(path) {
return resolve(__dirname, path);
}

/**
* Read json from the root of the project.
* @param {string} path Path from the root
*/
function readJson(path) {
return JSON.parse(readFileSync(resolvePath(`../${path}`), "utf8"));
}

/**
* Write json to file
* @param {string} target Path from the root
* @param {object} json JSON object
*/
function writeJson(target, json) {
writeFileSync(target, JSON.stringify(json), e => {
console.error(e);
});
}

export {
__dirname,
getBanner,
readJson,
resolvePath,
writeJson
};
@@ -1,6 +1,6 @@
const {merge, mergeWithCustomize, customizeObject} = require("webpack-merge");
const WriteFilePlugin = require("write-file-webpack-plugin");
const plugin = require("./plugin")({});
const plugin = require("./plugin.cjs")({});
const path = require("path");

const config = {
Expand Down
7 changes: 4 additions & 3 deletions config/webpack/packaged.js → config/webpack/packaged.cjs
@@ -1,12 +1,13 @@
const {merge} = require("webpack-merge");
const webpack = require("webpack");
const TerserPlugin = require("terser-webpack-plugin");
const terserConfig = require("../terserConfig");
const banner = require("../banner");
const terserConfig = require("../terserConfig.cjs");
const banner = require("../banner.cjs");


const config = {
entry: {
"billboard.pkgd": ["core-js/stable", "regenerator-runtime/runtime", "./src/index.ts"],
"billboard.pkgd": ["core-js/stable", "./src/index.ts"],
"billboard.pkgd.min": ["core-js/stable", "./src/index.ts"]
},
optimization: {
Expand Down
25 changes: 18 additions & 7 deletions config/webpack/plugin.js → config/webpack/plugin.cjs
Expand Up @@ -4,8 +4,8 @@ const fs = require("fs");
const path = require("path");
const CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const terserConfig = require("../terserConfig");
const banner = require("../banner");
const terserConfig = require("../terserConfig.cjs");
const banner = require("../banner.cjs");

const srcPath = "./src/Plugin/";
const distPath = path.resolve(__dirname, "../../dist/plugin/");
Expand Down Expand Up @@ -39,18 +39,28 @@ const config = {
banner: banner.production + banner.plugin,
entryOnly: true
})
]
],
};

module.exports = (common, env) => {
if (env && env.MIN) {
const MODE = env?.MODE;

if (env && MODE === "min") {
config.output.filename = config.output.filename.replace(".js", ".min.js");

config.optimization = {
usedExports: true,
minimize: true,
minimizer: [new TerserPlugin(terserConfig)]
};
} else if (env && MODE === "pkgd") {
delete common.externals;

config.output.path = `${distPath}/pkgd`;

for (const key in config.entry) {
config.entry[key] = ["core-js/stable", config.entry[key]];
}
} else {
config.plugins.push(new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [distPath],
Expand All @@ -60,11 +70,12 @@ module.exports = (common, env) => {
}));
}

return mergeWithCustomize({
const r = mergeWithCustomize({
customizeObject: customizeObject({
entry: "replace",
output: "replace",
module: "replace"
output: "replace"
})
})(common, config);

return r;
};
Expand Up @@ -3,8 +3,8 @@ const webpack = require("webpack");
const path = require("path");
const CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const terserConfig = require("../terserConfig");
const banner = require("../banner");
const terserConfig = require("../terserConfig.cjs");
const banner = require("../banner.cjs");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

Expand Down
2 changes: 1 addition & 1 deletion config/webpack/theme.js → config/webpack/theme.cjs
Expand Up @@ -4,7 +4,7 @@ const fs = require("fs");
const path = require("path");
const CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin;
const WebpackCleanPlugin = require("webpack-clean");
const banner = require("../banner");
const banner = require("../banner.cjs");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

Expand Down
2 changes: 2 additions & 0 deletions karma.conf.js → karma.conf.ts
@@ -1,3 +1,5 @@
/* eslint-disable */
// @ts-nocheck
const webpack = require("webpack");
const isWin = require("os").platform() === "win32";

Expand Down

0 comments on commit 437c007

Please sign in to comment.