[READ-ONLY] Mirror of https://github.com/danielroe/siroc. Zero-config build tooling for Node
bundle node package rollup typescript
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: add support for multiple entrypoints using `exports`

* add support for umd `browser` field in `package.json`

closes #71

Daniel Roe (Oct 11, 2020, 11:32 PM +0100) 4bc4d1db d6de0d84

+242 -64
+7 -1
README.md
··· 86 86 At the most basic level, your entrypoints are configured in your `package.json`: 87 87 88 88 - `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin)) 89 - - `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main)) 89 + - `main`, `module` and `browser` (see [npm docs](https://docs.npmjs.com/files/package.json#main)) 90 + - `types` if you want a TS declaration file to be generated for your main/module/browser entrypoints 91 + - `exports` (see [npm docs](https://nodejs.org/api/packages.html#packages_conditional_exports)) 92 + 93 + There are some conventions in place of configuration that are worth noting: 94 + * the file type is inferred from the file name if possible (e.g. `babel.es.js` will be in 'es' format) 95 + * `main` defaults to CJS, `module` to ES, `browser` to UMD, and `bin` to CJS 90 96 91 97 #### Build hooks 92 98
+79 -53
src/core/build/rollup.ts
··· 15 15 import { Package } from '../package' 16 16 import { includeDefinedProperties, includeIf } from '../utils' 17 17 import { builtins } from './builtins' 18 - import { getNameFunction } from './utils' 18 + import { convertToUMDName, getNameFunction } from './utils' 19 19 20 20 const __NODE_ENV__ = process.env.NODE_ENV 21 21 ··· 48 48 esbuildOptions, 49 49 ...options 50 50 }: BuildConfigOptions, 51 - { 51 + pkg: Package = new Package() 52 + ): RollupOptions[] { 53 + const { 52 54 binaries, 53 55 entrypoint, 54 - pkg, 56 + exports, 57 + pkg: pkgConfig, 55 58 options: { rootDir, suffix }, 56 - }: Package = new Package() 57 - ): RollupOptions[] { 59 + } = pkg 58 60 const resolvePath = (...path: string[]) => resolve(rootDir, ...path) 59 61 input = input ? resolvePath(input) : entrypoint 60 - if (!input && !binaries.length) return [] 62 + if (!input && !binaries.length && !exports.length) return [] 61 63 62 - const name = basename(pkg.name.replace(suffix, '')) 64 + const name = basename(pkgConfig.name.replace(suffix, '')) 63 65 const getFilenames = getNameFunction(rootDir, name) 64 66 65 67 const external = [ 66 68 // Dependencies that will be installed alongside the package 67 - ...Object.keys(pkg.dependencies || {}), 68 - ...Object.keys(pkg.optionalDependencies || {}), 69 - ...Object.keys(pkg.peerDependencies || {}), 69 + ...Object.keys(pkgConfig.dependencies || {}), 70 + ...Object.keys(pkgConfig.optionalDependencies || {}), 71 + ...Object.keys(pkgConfig.peerDependencies || {}), 70 72 // Builtin node modules 71 73 ...builtins, 72 74 ...externals, 75 + ] 76 + 77 + const getDeclarationPlugins = () => [ 78 + jsonPlugin(), 79 + dts({ 80 + compilerOptions: { 81 + allowJs: true, 82 + }, 83 + }), 73 84 ] 74 85 75 86 const getPlugins = () => ··· 96 107 97 108 const defaultOutputs: OutputOptions[] = [ 98 109 { 99 - ...getFilenames(pkg.main), 100 - format: 'cjs', 110 + ...getFilenames(pkgConfig.main), 101 111 preferConst: true, 102 112 exports: 'auto', 103 113 }, 104 114 ...includeIf( 105 - !dev && pkg.module, 115 + !dev && pkgConfig.module, 106 116 (pkgModule): OutputOptions => ({ 107 - ...getFilenames(pkgModule, '-es'), 108 - format: 'es', 117 + ...getFilenames(pkgModule, '.es', 'es'), 118 + exports: 'auto', 119 + }) 120 + ), 121 + ...includeIf( 122 + !dev && pkgConfig.browser, 123 + (pkgBrowser): OutputOptions => ({ 124 + ...getFilenames(pkgBrowser, '.umd', 'umd'), 125 + name: convertToUMDName(pkgConfig.name), 109 126 exports: 'auto', 110 127 }) 111 128 ), ··· 113 130 114 131 return [ 115 132 ...binaries.map(([binary, input]) => { 116 - return defu<RollupOptions>( 117 - {}, 118 - options as RollupOptions, 119 - { 120 - input, 121 - output: { 122 - ...getFilenames(binary), 123 - format: 'cjs', 124 - preferConst: true, 125 - exports: 'auto', 126 - banner: '#!/usr/bin/env node\n', 127 - }, 128 - external, 129 - plugins: getPlugins(), 130 - } as RollupOptions 131 - ) 133 + return defu<RollupOptions>({}, options as RollupOptions, { 134 + input, 135 + output: { 136 + ...getFilenames(binary, '', 'cjs'), 137 + preferConst: true, 138 + exports: 'auto', 139 + banner: '#!/usr/bin/env node\n', 140 + } as OutputOptions, 141 + external, 142 + plugins: getPlugins(), 143 + }) 132 144 }), 133 145 ...includeIf(input, input => 134 - defu<RollupOptions>( 135 - {}, 136 - options as RollupOptions, 137 - { 138 - input, 139 - output: defaultOutputs, 140 - external, 141 - plugins: getPlugins(), 142 - } as RollupOptions 143 - ) 146 + defu<RollupOptions>({}, options as RollupOptions, { 147 + input, 148 + output: defaultOutputs, 149 + external, 150 + plugins: getPlugins(), 151 + }) 144 152 ), 145 - ...includeIf(pkg.types && input, input => ({ 153 + ...includeIf(pkgConfig.types && input, input => ({ 146 154 input, 147 155 output: { 148 - file: resolvePath(pkg.types || ''), 149 - format: 'es' as const, 156 + // eslint-disable-next-line 157 + file: resolvePath(pkgConfig.types!), 158 + format: 'es', 150 159 exports: 'auto', 151 - } as RollupOptions, 160 + } as OutputOptions, 152 161 external, 153 - plugins: [ 154 - jsonPlugin(), 155 - dts({ 156 - compilerOptions: { 157 - allowJs: true, 158 - }, 159 - }), 160 - ], 162 + plugins: getDeclarationPlugins(), 161 163 })), 164 + ...exports.map(outfile => 165 + defu<RollupOptions>({}, options as RollupOptions, { 166 + input: pkg.resolveEntrypoint(outfile), 167 + output: { 168 + ...getFilenames(outfile), 169 + preferConst: true, 170 + exports: 'auto', 171 + } as OutputOptions, 172 + external, 173 + plugins: getPlugins(), 174 + }) 175 + ), 176 + ...exports.map(outfile => 177 + defu<RollupOptions>({}, options as RollupOptions, { 178 + input: pkg.resolveEntrypoint(outfile), 179 + output: { 180 + file: resolvePath(outfile.replace('.js', '.d.ts')), 181 + format: 'es', 182 + exports: 'auto', 183 + } as OutputOptions, 184 + external, 185 + plugins: getDeclarationPlugins(), 186 + }) 187 + ), 162 188 ] 163 189 }
+31
src/core/build/utils.spec.ts
··· 1 + import { convertToUMDName, formatForName } from './utils' 2 + 3 + describe('formatForName()', () => { 4 + const fixture = [ 5 + [['index.js'], 'cjs'], 6 + [['index.es.js'], 'es'], 7 + [['index.umd.js'], 'umd'], 8 + [['index.js', 'es'], 'es'], 9 + [['index.browser.js'], 'cjs'], 10 + ] as const 11 + 12 + it('should return the correct format', () => { 13 + fixture.forEach(([[filename, ...options], output]) => { 14 + expect(formatForName(filename, ...options)).toEqual(output) 15 + }) 16 + }) 17 + }) 18 + 19 + describe('convertToUMDName()', () => { 20 + const fixture = [ 21 + ['@vue/composition-api', 'CompositionApi'], 22 + ['sanity-typed-queries', 'SanityTypedQueries'], 23 + ['siroc', 'Siroc'], 24 + ] as const 25 + 26 + it('should return the correct names', () => { 27 + fixture.forEach(([input, output]) => { 28 + expect(convertToUMDName(input)).toEqual(output) 29 + }) 30 + }) 31 + })
+42 -1
src/core/build/utils.ts
··· 1 1 import { resolve, dirname, basename } from 'path' 2 + import type { OutputOptions } from 'rollup' 2 3 3 4 export const getNameFunction = (rootDir: string, packageName: string) => ( 4 5 filename: string | undefined, 5 - defaultSuffix = '' 6 + defaultSuffix = '', 7 + defaultFormat?: OutputFormat 6 8 ) => { 7 9 return { 8 10 dir: filename ··· 14 16 chunkFileNames: filename 15 17 ? `${basename(filename)}-[name].js` 16 18 : `${packageName}-[name]${defaultSuffix}.js`, 19 + format: formatForName( 20 + filename ? basename(filename) : `${packageName}${defaultSuffix}.js`, 21 + defaultFormat 22 + ), 17 23 } as const 24 + } 25 + 26 + type OutputFormat = OutputOptions['format'] 27 + 28 + const formats: OutputFormat[] = [ 29 + 'amd', 30 + 'cjs', 31 + 'es', 32 + 'iife', 33 + 'system', 34 + 'umd', 35 + 'commonjs', 36 + 'esm', 37 + 'module', 38 + 'systemjs', 39 + ] 40 + 41 + export const formatForName = ( 42 + filename: string, 43 + defaultFormat: OutputFormat = 'cjs' 44 + ): OutputFormat => { 45 + const parts = filename.split('.') 46 + if (parts.length > 2) { 47 + const format = parts[parts.length - 2] as OutputFormat 48 + if (formats.includes(format)) return format 49 + } 50 + return defaultFormat 51 + } 52 + 53 + export const convertToUMDName = (name: string) => { 54 + const unnamspacedName = name.split('/').pop() 55 + 56 + return (unnamspacedName || name) 57 + .replace(/-./g, r => r[1].toUpperCase()) 58 + .replace(/^./, r => r.toUpperCase()) 18 59 }
+18 -9
src/core/package/index.ts
··· 1 - import { basename, dirname, relative, resolve } from 'path' 1 + import { dirname, relative, resolve } from 'path' 2 2 3 3 import { bold } from 'chalk' 4 4 import consola, { Consola } from 'consola' ··· 29 29 RequireProperties, 30 30 } from '../utils' 31 31 import type { PackageJson } from './types' 32 + import { getEntrypointFilenames } from './utils' 32 33 33 34 export interface DefaultPackageOptions { 34 35 /** ··· 382 383 return execa.command(command, options) 383 384 } 384 385 386 + get exports(): string[] { 387 + const { exports } = this.pkg 388 + 389 + if (!exports) return [] 390 + if (typeof exports === 'string') return [exports] 391 + if (Array.isArray(exports)) return exports 392 + 393 + return Object.values(exports) 394 + .map(ex => (typeof ex === 'string' ? ex : Object.values(ex))) 395 + .flat() 396 + } 397 + 385 398 /** 386 399 * Execute command in the package root directory 387 400 */ ··· 408 421 } 409 422 } 410 423 411 - private resolveEntrypoint(path = this.pkg.main) { 424 + resolveEntrypoint(path = this.pkg.main) { 412 425 if (!path) return undefined 413 426 414 - const basefile = basename(path).split('.').slice(0, -1).join() 415 427 let input!: string 416 - const filenames = [basefile, `${basefile}/index`, 'index'] 417 - .map(name => [`${name}.ts`, `${name}.js`]) 418 - .reduce((names, arr) => { 419 - arr.forEach(name => names.push(name)) 420 - return names 421 - }, [] as string[]) 428 + const filenames = getEntrypointFilenames(path) 429 + 422 430 filenames.some(filename => { 423 431 input = this.resolvePath('src', filename) 424 432 return existsSync(input) 425 433 }) 434 + 426 435 return input 427 436 } 428 437
+38
src/core/package/utils.spec.ts
··· 1 + import { getEntrypointFilenames } from './utils' 2 + 3 + describe('getEntrypointFilenames()', () => { 4 + const fixture = [ 5 + [ 6 + './lib/babel.js', 7 + [ 8 + 'babel.ts', 9 + 'babel.js', 10 + 'babel/index.ts', 11 + 'babel/index.js', 12 + 'index.ts', 13 + 'index.js', 14 + ], 15 + ], 16 + [ 17 + './lib/babel.es.js', 18 + [ 19 + 'babel.es.ts', 20 + 'babel.es.js', 21 + 'babel.es/index.ts', 22 + 'babel.es/index.js', 23 + 'babel.ts', 24 + 'babel.js', 25 + 'babel/index.ts', 26 + 'babel/index.js', 27 + 'index.ts', 28 + 'index.js', 29 + ], 30 + ], 31 + ] as const 32 + 33 + it('should return the correct possible filenames', () => { 34 + fixture.forEach(([name, output]) => { 35 + expect(getEntrypointFilenames(name)).toEqual(output) 36 + }) 37 + }) 38 + })
+24
src/core/package/utils.ts
··· 1 + import { basename } from 'path' 2 + 3 + export const getEntrypointFilenames = (path: string) => { 4 + if (path.startsWith('./')) path = path.slice(2) 5 + 6 + const basefile = basename(path).split('.').slice(0, -1).join('.') 7 + const withoutType = basefile.split('.').slice(0, 1).join('') 8 + const filenames = Array.from( 9 + new Set([ 10 + basefile, 11 + `${basefile}/index`, 12 + withoutType, 13 + `${withoutType}/index`, 14 + 'index', 15 + ]) 16 + ) 17 + .map(name => [`${name}.ts`, `${name}.js`]) 18 + .reduce((names, arr) => { 19 + arr.forEach(name => names.push(name)) 20 + return names 21 + }, [] as string[]) 22 + 23 + return filenames 24 + }
+3
src/core/build/__snapshots__/rollup.spec.ts.snap
··· 5 5 "chunkFileNames": "fname-[name].js", 6 6 "dir": "/customdist", 7 7 "entryFileNames": "fname", 8 + "format": "cjs", 8 9 } 9 10 `; 10 11 ··· 13 14 "chunkFileNames": "pkg-[name].js", 14 15 "dir": "/dist", 15 16 "entryFileNames": "pkg.js", 17 + "format": "cjs", 16 18 } 17 19 `; 18 20 ··· 21 23 "chunkFileNames": "pkg-[name]-suffix.js", 22 24 "dir": "/dist", 23 25 "entryFileNames": "pkg-suffix.js", 26 + "format": "cjs", 24 27 } 25 28 `;