Skip to content

Commit

Permalink
feat: shebang support
Browse files Browse the repository at this point in the history
resolves #27
  • Loading branch information
pi0 committed Dec 3, 2021
1 parent 565613a commit 12ccc15
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 7 deletions.
2 changes: 0 additions & 2 deletions bin/unbuild.mjs

This file was deleted.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
"exports": "./dist/index.mjs",
"types": "dist/index.d.ts",
"bin": {
"unbuild": "./bin/unbuild.mjs"
"unbuild": "./dist/cli.mjs"
},
"files": [
"bin",
"dist"
],
"scripts": {
Expand Down
60 changes: 60 additions & 0 deletions src/builder/plugins/shebang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { promises as fsp } from 'fs'
import MagicString from 'magic-string'
import { resolve } from 'pathe'
import type { Plugin } from 'rollup'

// Forked from https://github.com/developit/rollup-plugin-preserve-shebang (1.0.1 @ MIT)

const SHEBANG_RE = /^#![^\n]*/

export interface ShebangPluginOptions {
preserve?: Boolean
}

export function shebangPlugin (options: ShebangPluginOptions = {}): Plugin {
const shebangs = new Map()
return {
name: 'unbuild-shebang',
// @ts-ignore temp workaround
_options: options,
transform (code, mod) {
let shebang
code = code.replace(SHEBANG_RE, (match) => {
shebang = match
return ''
})
if (!shebang) { return null }
shebangs.set(mod, shebang)
return { code, map: null }
},
renderChunk (code, chunk, { sourcemap }) {
if (options.preserve === false) { return null }
const shebang = shebangs.get(chunk.facadeModuleId)
if (!shebang) { return null }
const s = new MagicString(code)
s.prepend(`${shebang}\n`)
return {
code: s.toString(),
map: sourcemap ? s.generateMap({ hires: true }) : null
}
},
async writeBundle (options, bundle) {
for (const [fileName, output] of Object.entries(bundle)) {
if (output.type !== 'chunk') { continue }
if (output.code?.match(SHEBANG_RE)) {
const outFile = resolve(options.dir!, fileName)
await makeExecutable(outFile)
}
}
}
}
}

export async function makeExecutable (filePath: string) {
await fsp.chmod(filePath, 0o755 /* rwx r-x r-x */).catch(() => {})
}

export function getShebang (code: string, append = '\n') {
const m = code.match(SHEBANG_RE)
return m ? m + append : ''
}
24 changes: 21 additions & 3 deletions src/builder/rollup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { writeFile } from 'fs/promises'
import { promises as fsp } from 'fs'
import type { RollupOptions, OutputOptions, OutputChunk } from 'rollup'
import { rollup } from 'rollup'
import commonjs from '@rollup/plugin-commonjs'
Expand All @@ -8,11 +9,12 @@ import _esbuild from 'rollup-plugin-esbuild'
import dts from 'rollup-plugin-dts'
import { relative, resolve } from 'pathe'
import consola from 'consola'
import { getpkg } from '../utils'
import { getpkg, tryResolve } from '../utils'
import type { BuildContext } from '../types'
import { JSONPlugin } from './plugins/json'
import { rawPlugin } from './plugins/raw'
import { cjsPlugin } from './plugins/cjs'
import { shebangPlugin, makeExecutable, getShebang } from './plugins/shebang'

// @ts-ignore https://github.com/unjs/unbuild/issues/23
const esbuild = _esbuild.default || _esbuild
Expand All @@ -21,11 +23,21 @@ export async function rollupBuild (ctx: BuildContext) {
if (ctx.options.stub) {
for (const entry of ctx.options.entries.filter(entry => entry.builder === 'rollup')) {
const output = resolve(ctx.options.rootDir, ctx.options.outDir, entry.name!)

const resolvedEntry = tryResolve(entry.input, ctx.options.rootDir) || entry.input
const code = await fsp.readFile(resolvedEntry, 'utf8')
const shebang = getShebang(code)

if (ctx.options.rollup.emitCJS) {
await writeFile(output + '.cjs', `module.exports = require('jiti')(null, { interopDefault: true })('${entry.input}')`)
await writeFile(output + '.cjs', `${shebang}module.exports = require('jiti')(null, { interopDefault: true })('${entry.input}')`)
}
await writeFile(output + '.mjs', `import jiti from 'jiti';\nexport default jiti(null, { interopDefault: true })('${entry.input}');`)
await writeFile(output + '.mjs', `${shebang}import jiti from 'jiti';\nexport default jiti(null, { interopDefault: true })('${entry.input}');`)
await writeFile(output + '.d.ts', `export * from '${entry.input}';\nexport { default } from '${entry.input}';`)

if (shebang) {
await makeExecutable(output + '.cjs')
await makeExecutable(output + '.mjs')
}
}
await ctx.hooks.callHook('rollup:done', ctx)
return
Expand Down Expand Up @@ -61,6 +73,9 @@ export async function rollupBuild (ctx: BuildContext) {
// Types
if (ctx.options.declaration) {
rollupOptions.plugins = rollupOptions.plugins || []
// TODO: Use fresh rollup options
const shebangPlugin: any = rollupOptions.plugins.find(p => p && p.name === 'unbuild-shebang')
shebangPlugin._options.preserve = false
rollupOptions.plugins.push(dts({ respectExternal: true }))
await ctx.hooks.callHook('rollup:dts:options', ctx, rollupOptions)
const typesBuild = await rollup(rollupOptions)
Expand Down Expand Up @@ -143,6 +158,8 @@ export function getRollupOptions (ctx: BuildContext): RollupOptions {
preferConst: true
}),

shebangPlugin(),

esbuild({
target: 'es2020'
}),
Expand All @@ -158,6 +175,7 @@ export function getRollupOptions (ctx: BuildContext): RollupOptions {
ctx.options.rollup.cjsBridge && cjsPlugin({}),

rawPlugin()

].filter(Boolean)
} as RollupOptions
}
1 change: 1 addition & 0 deletions src/cli.ts
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/env node
import { resolve } from 'pathe'
import mri from 'mri'
import { build } from './build'
Expand Down
12 changes: 12 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,15 @@ export function tryRequire (id: string, rootDir: string = process.cwd()) {
return {}
}
}

export function tryResolve (id: string, rootDir: string = process.cwd()) {
const _require = jiti(rootDir, { interopDefault: true })
try {
return _require.resolve(id)
} catch (err: any) {
if (err.code !== 'MODULE_NOT_FOUND') {
console.error(`Error trying import ${id} from ${rootDir}`, err)
}
return id
}
}

0 comments on commit 12ccc15

Please sign in to comment.