Skip to content

Commit

Permalink
New(Pack): Bundle modules based on bundle export name, like `index.bu…
Browse files Browse the repository at this point in the history
…ndle.js`, `index.bundle.css`
  • Loading branch information
1aron committed Jul 4, 2023
1 parent b53f82f commit 3d4eb97
Show file tree
Hide file tree
Showing 15 changed files with 130 additions and 38 deletions.
43 changes: 25 additions & 18 deletions packages/pack/src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ program.command('pack [entryPaths...]', { isDefault: true })
.option('-p, --platform <node,browser,neutral>', 'Platform target', 'browser')
.option('-o, --outdir <dir>', 'The output directory for the build operation', 'dist')
.option('--serve', 'Serve mode starts a web server that serves your code to your browser on your device', false)
.option('--bundle', 'Inline any imported dependencies into the file itself', false)
.option('-e, --external <packages...>', 'External packages to exclude from the build', externalDependencies)
.option('-ee, --extra-external <packages...>', 'Extra external packages to exclude from the build', [])
.option('-re, --resolve-extensions [extensions...]', 'The resolution algorithm used by node supports implicit file extensions', ['.tsx', '.ts', '.jsx', '.js', '.css', '.json'])
Expand All @@ -55,7 +56,6 @@ program.command('pack [entryPaths...]', { isDefault: true })
.option('--target [targets...]', 'This sets the target environment for the generated JavaScript and/or CSS code.')
.option('--mangle-props', 'Pass a regular expression to esbuild to tell esbuild to automatically rename all properties that match this regular expression', '^_')
.option('--no-declare', 'OFF: Emit typescript declarations', !!pkg.types)
.option('--no-bundle', 'OFF: Inline any imported dependencies into the file itself', true)
.option('--no-minify', 'OFF: Minify the generated code')
.option('--no-clean', 'OFF: Clean up the previous output directory before the build starts')
.action(async function (entries: string[], options, args) {
Expand All @@ -66,32 +66,37 @@ program.command('pack [entryPaths...]', { isDefault: true })
}
const useConfig = exploreConfig('techor.*')
const buildTasks: BuildTask[] = []
const getFileSrcGlobPattern = (filePath: string, targetExt: string) => {
const subFilePath /* components/a.ts */ = path.relative(options.outdir, filePath)
const resolvePackageEntry = (filePath: string, targetExt: string) => {
const subFilePath /* components/a.ts */ = path.relative(options.outdir, filePath.replace('.bundle', ''))
const srcFilePath /* src/components/a.ts */ = path.join(options.srcdir, subFilePath)
return path.changeExt(srcFilePath, targetExt)
}
const addBuildTask = async (eachEntries: string[], eachOptions: { format: string, softBundle?: boolean, ext?: string, platform?: string, outdir?: string, outFile?: string }) => {
const addBuildTask = async (eachEntries: string[], eachOptions: { format: string, bundle?: boolean, softBundle?: boolean, ext?: string, platform?: string, outdir?: string, outFile?: string }) => {
const isCSSTask = eachOptions.format === 'css'
let eachOutExt = eachOptions.ext || eachOptions.outFile && path.extname(eachOptions.outFile) || undefined
if (!eachOutExt) {
eachOutExt = { cjs: options.cjsExt, esm: options.esmExt, iife: options.iifeExt }[eachOptions.format]
}
if (eachOptions.bundle === undefined) {
eachOptions.bundle = options.bundle
}
const external = [
...options.external,
...options.extraExternal
]
const eachOutdir = eachOptions.outdir || options.outdir
if (options.bundle && eachOptions.softBundle) {
if (eachOptions.bundle && eachOptions.softBundle) {
external.push('.*')
}

console.log(eachOptions.outFile)
const buildOptions: BuildOptions = extend(options, {
outExtension: isCSSTask
? { '.css': '.css' }
: { '.js': eachOutExt },
logLevel: 'info',
outdir: eachOutdir,
outdir: eachOptions.outFile ? undefined : eachOutdir,
bundle: eachOptions.bundle,
outfile: eachOptions.outFile,
outbase: options.srcdir,
platform: eachOptions.platform || options.platform,
metafile: true,
Expand All @@ -103,13 +108,13 @@ program.command('pack [entryPaths...]', { isDefault: true })
sourcemap: options.sourcemap,
external,
plugins: [],
}, useConfig?.pack)
} as BuildOptions, useConfig?.pack)

if (!buildOptions.target) {
delete buildOptions.target
}

if (!options.bundle) {
if (!eachOptions.bundle) {
delete buildOptions.external
}

Expand Down Expand Up @@ -145,6 +150,7 @@ program.command('pack [entryPaths...]', { isDefault: true })
&& eachBuildTask.options.format === buildOptions.format
&& isEqual(eachBuildTask.options.outExtension, buildOptions.outExtension)
&& eachBuildTask.options.outdir === buildOptions.outdir
&& eachBuildTask.options.outfile === buildOptions.outfile
)
)
if (!buildOptions.entryPoints.length) {
Expand Down Expand Up @@ -198,7 +204,7 @@ program.command('pack [entryPaths...]', { isDefault: true })
options.shakableFormat.forEach((eachFormat: string) =>
addBuildTask(
[path.join(options.srcdir, '**/*.{js,ts,jsx,tsx,mjs,mts}')],
{ format: eachFormat, platform: 'node', outdir: path.join(options.outdir, eachFormat), softBundle: true }
{ format: eachFormat, platform: 'node', outdir: path.join(options.outdir), softBundle: true }
))
}

Expand All @@ -216,10 +222,11 @@ program.command('pack [entryPaths...]', { isDefault: true })
(function handleExports(eachExports: any, eachParentKey: string, eachOptions?: { format?: string, outFile?: string, platform?: string }) {
if (typeof eachExports === 'string') {
const exportsExt = path.extname(eachExports).slice(1)
addBuildTask([getFileSrcGlobPattern(eachExports, '.{js,ts,jsx,tsx,mjs,mts}')], {
addBuildTask([resolvePackageEntry(eachExports, '.{js,ts,jsx,tsx,mjs,mts}')], {
format: ext2format[exportsExt],
outFile: options.outFile || eachExports,
platform: options.platform
platform: options.platform,
bundle: eachExports.includes('.bundle')
})
} else {
for (const eachExportKey in eachExports) {
Expand Down Expand Up @@ -266,24 +273,24 @@ program.command('pack [entryPaths...]', { isDefault: true })
})(pkg.exports, '')
}
if (pkg.style) {
addBuildTask([getFileSrcGlobPattern(pkg.main, '.css')], { format: 'css' })
addBuildTask([resolvePackageEntry(pkg.style, '.css')], { format: 'css', outFile: pkg.style, bundle: pkg.main.includes('.bundle') })
}
if (pkg.main && !pkg.main.endsWith('.css')) {
addBuildTask([getFileSrcGlobPattern(pkg.main, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'cjs', outFile: pkg.main })
addBuildTask([resolvePackageEntry(pkg.main, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'cjs', outFile: pkg.main, bundle: pkg.main.includes('.bundle') })
}
if (pkg.module) {
addBuildTask([getFileSrcGlobPattern(pkg.module, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'esm', outFile: pkg.module })
addBuildTask([resolvePackageEntry(pkg.module, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'esm', outFile: pkg.module, bundle: pkg.module.includes('.bundle') })
}
if (pkg.browser) {
addBuildTask([getFileSrcGlobPattern(pkg.browser, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'iife', platform: 'browser', outFile: pkg.browser })
addBuildTask([resolvePackageEntry(pkg.browser, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'iife', platform: 'browser', outFile: pkg.browser, bundle: pkg.browser.includes('.bundle') })
}
if (pkg.bin) {
if (typeof pkg.bin === 'string') {
addBuildTask([getFileSrcGlobPattern(pkg.bin, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'cjs', platform: 'node', outFile: pkg.bin })
addBuildTask([resolvePackageEntry(pkg.bin, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'cjs', platform: 'node', outFile: pkg.bin, bundle: pkg.bin.includes('.bundle') })
} else {
for (const eachCommandName in pkg.bin) {
const eachCommandFile = pkg.bin[eachCommandName]
addBuildTask([getFileSrcGlobPattern(eachCommandFile, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'cjs', platform: 'node', outFile: eachCommandFile })
addBuildTask([resolvePackageEntry(eachCommandFile, '.{js,ts,jsx,tsx,mjs,mts}')], { format: 'cjs', platform: 'node', outFile: eachCommandFile, bundle: eachCommandFile.includes('.bundle') })
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/pack/tests/args-externals/pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { expectFileIncludes } from '../../../../utils/expect-file-includes'

test('prevent bundling external packages by args', () => {
execSync('tsx ../../src/bin pack --shakable --external @master/css @master/style-element.react', { cwd: __dirname, stdio: 'pipe' })
expectFileIncludes('dist/cjs/index.js', [
expectFileIncludes('dist/index.js', [
'require("@master/css")',
'require("@master/style-element.react")'
], { cwd: __dirname })
], { cwd: __dirname })
})
9 changes: 9 additions & 0 deletions packages/pack/tests/css-bundle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@techor.tests/css-bundle",
"private": true,
"main": "./dist/index.bundle.css",
"style": "./dist/index.bundle.css",
"files": [
"dist"
]
}
13 changes: 13 additions & 0 deletions packages/pack/tests/css-bundle/src/float.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@keyframes float {
0% {
transform: none;
}

50% {
transform: translateY(-1.25rem);
}

100% {
transform: none;
}
}
21 changes: 21 additions & 0 deletions packages/pack/tests/css-bundle/src/heart.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@keyframes heart {
0% {
transform: scale(1);
}

14% {
transform: scale(1.3);
}

28% {
transform: scale(1);
}

42% {
transform: scale(1.3);
}

70% {
transform: scale(1);
}
}
2 changes: 2 additions & 0 deletions packages/pack/tests/css-bundle/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@import 'heart';
@import 'float';
7 changes: 7 additions & 0 deletions packages/pack/tests/css-bundle/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { execSync } from 'node:child_process'
import { expectExist } from '../../../../utils/expect-exist'

test('specify css entries', () => {
execSync('tsx ../../src/bin pack', { cwd: __dirname, stdio: 'pipe' })
expectExist(['dist/index.bundle.css'])
})
13 changes: 13 additions & 0 deletions packages/pack/tests/css/src/float.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@keyframes float {
0% {
transform: none;
}

50% {
transform: translateY(-1.25rem);
}

100% {
transform: none;
}
}
21 changes: 21 additions & 0 deletions packages/pack/tests/css/src/heart.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@keyframes heart {
0% {
transform: scale(1);
}

14% {
transform: scale(1.3);
}

28% {
transform: scale(1);
}

42% {
transform: scale(1.3);
}

70% {
transform: scale(1);
}
}
5 changes: 2 additions & 3 deletions packages/pack/tests/css/src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
body {
background-color: red
}
@import 'heart';
@import 'float';
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import { execSync } from 'node:child_process'
test('extract css entries from `package.json`', () => {
execSync('tsx ../../src/bin pack', { cwd: __dirname, stdio: 'pipe' })
expectExist(['dist/index.css'])
expectFileIncludes('dist/index.css', ['body{background-color:red}'], { cwd: __dirname })
expectFileIncludes('dist/index.css', ['@import"heart";@import"float";'], { cwd: __dirname })
})
4 changes: 2 additions & 2 deletions packages/pack/tests/options/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { expectFileIncludes } from '../../../../utils/expect-file-includes'

it('mangle private', () => {
execSync('tsx ../../src/bin pack --no-minify --shakable', { cwd: __dirname, stdio: 'pipe' })
expectFileExcludes('dist/esm/index.mjs', [
expectFileExcludes('dist/index.mjs', [
'_fullAAAMembership'
], { cwd: __dirname })
})

it('tree shake and only bundle BBB', () => {
execSync('tsx ../../src/bin pack src/tree-shaking.ts --no-minify --no-clean --shakable', { cwd: __dirname, stdio: 'pipe' })
expectFileExcludes('dist/esm/tree-shaking.mjs', [
expectFileExcludes('dist/tree-shaking.mjs', [
'AAA'
], { cwd: __dirname })
})
6 changes: 3 additions & 3 deletions packages/pack/tests/standard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"name": "@techor.tests/standard",
"private": true,
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"main": "./dist/index.bundle.js",
"module": "./dist/index.bundle.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
Expand All @@ -25,4 +25,4 @@
"peerDependencies": {
"@techor/extend": ""
}
}
}
16 changes: 8 additions & 8 deletions packages/pack/tests/standard/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import path from 'path'

beforeAll(() => {
execSync('tsx ../../src/bin pack --platform node --no-minify --shakable', { cwd: __dirname, stdio: 'pipe' })
execSync('esbuild src/index.ts --bundle --outfile=esbuild-dist/index.js --format=cjs --platform=node --external:@techor/log --external:@techor/extend', { cwd: __dirname, stdio: 'pipe' })
execSync('esbuild src/index.ts --bundle --outfile=esbuild-dist/index.mjs --format=esm --platform=node --external:@techor/log --external:@techor/extend', { cwd: __dirname, stdio: 'pipe' })
execSync('esbuild src/index.ts --bundle --outfile=esbuild-dist/index.bundle.js --format=cjs --platform=node --external:@techor/log --external:@techor/extend', { cwd: __dirname, stdio: 'pipe' })
execSync('esbuild src/index.ts --bundle --outfile=esbuild-dist/index.bundle.mjs --format=esm --platform=node --external:@techor/log --external:@techor/extend', { cwd: __dirname, stdio: 'pipe' })
})

it('standard package outputs', () => {
expect(fs.readFileSync(path.join(__dirname, 'dist/index.js')).toString()).toContain('module.exports = __toCommonJS')
expect(fs.readFileSync(path.join(__dirname, 'dist/index.mjs')).toString()).toContain(dedent`
expect(fs.readFileSync(path.join(__dirname, 'dist/index.bundle.js')).toString()).toContain('module.exports = __toCommonJS')
expect(fs.readFileSync(path.join(__dirname, 'dist/index.bundle.mjs')).toString()).toContain(dedent`
export {
optionA,
optionB
};
`)
expect(fs.readFileSync(path.join(__dirname, 'dist/cjs/index.js')).toString()).toContain('module.exports = __toCommonJS(src_exports);')
expect(fs.readFileSync(path.join(__dirname, 'dist/esm/index.mjs')).toString()).toContain('export * from "./options/index.mjs";')
expect(fs.readFileSync(path.join(__dirname, 'dist/index.js')).toString()).toContain('module.exports = __toCommonJS(src_exports);')
expect(fs.readFileSync(path.join(__dirname, 'dist/index.mjs')).toString()).toContain('export * from "./options/index.mjs";')
})

it('cjs bundle should be same as esbuild', () => {
expect(fs.readFileSync(path.join(__dirname, 'dist/index.js')).toString()).toEqual(fs.readFileSync(path.join(__dirname, 'esbuild-dist/index.js')).toString())
expect(fs.readFileSync(path.join(__dirname, 'dist/index.bundle.js')).toString()).toEqual(fs.readFileSync(path.join(__dirname, 'esbuild-dist/index.bundle.js')).toString())
})

it('esm bundle should be same as esbuild', () => {
expect(fs.readFileSync(path.join(__dirname, 'dist/index.mjs')).toString()).toEqual(fs.readFileSync(path.join(__dirname, 'esbuild-dist/index.mjs')).toString())
expect(fs.readFileSync(path.join(__dirname, 'dist/index.bundle.mjs')).toString()).toEqual(fs.readFileSync(path.join(__dirname, 'esbuild-dist/index.bundle.mjs')).toString())
})
2 changes: 1 addition & 1 deletion packages/pack/tests/tsx/pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { expectExist } from '../../../../utils/expect-exist'

test('resolve `.tsx` with `package.json`', () => {
execSync('tsx ../../src/bin pack --shakable', { cwd: __dirname, stdio: 'pipe' })
expectExist(['dist/cjs/index.js'])
expectExist(['dist/index.js'])
})

0 comments on commit 3d4eb97

Please sign in to comment.