userland: add archive tools and pinned GoAWK, esbuild, and QuickJS
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
const http = require('http')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const fs = require('fs')
|
||||
|
||||
const js = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'lib', 'browser.js'))
|
||||
const jsMin = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'lib', 'browser.min.js'))
|
||||
const esm = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'esm', 'browser.js'))
|
||||
const esmMin = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'esm', 'browser.min.js'))
|
||||
const wasm = fs.readFileSync(path.join(__dirname, '..', '..', 'npm', 'esbuild-wasm', 'esbuild.wasm'))
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', '..', 'scripts', 'browser', 'index.html'))
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
console.log(`[http] ${req.method} ${req.url}`)
|
||||
|
||||
try {
|
||||
if (req.method === 'GET' && req.url) {
|
||||
const parsed = url.parse(req.url)
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/lib/browser.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(js)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/lib/browser.min.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(jsMin)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/esm/browser.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(esm)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/esm/browser.min.js') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
||||
res.end(esmMin)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/npm/esbuild-wasm/esbuild.wasm') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/wasm' })
|
||||
res.end(wasm)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/scripts/browser/index.html') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.end(html)
|
||||
return
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/scripts/browser/esbuild.wasm.bagel') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/octet-stream' })
|
||||
res.end(wasm)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(404)
|
||||
res.end('404 Not Found')
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
res.writeHead(500)
|
||||
res.end('500 Internal Server Error')
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
server.listen()
|
||||
const { address, port } = server.address()
|
||||
const serverURL = url.format({ protocol: 'http', hostname: address, port })
|
||||
console.log(`[http] listening on ${serverURL}`)
|
||||
|
||||
async function main() {
|
||||
let allTestsPassed = true
|
||||
try {
|
||||
const browser = await require('puppeteer').launch({
|
||||
// This is here because since December 2024, GitHub changed something about
|
||||
// their CI that causes this error:
|
||||
//
|
||||
// [FATAL:zygote_host_impl_linux.cc(128)] No usable sandbox! If you are running
|
||||
// on Ubuntu 23.10+ or another Linux distro that has disabled unprivileged user
|
||||
// namespaces with AppArmor, see https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md.
|
||||
// Otherwise see https://chromium.googlesource.com/chromium/src/+/main/docs/linux/suid_sandbox_development.md
|
||||
// for more information on developing with the (older) SUID sandbox. If you want
|
||||
// to live dangerously and need an immediate workaround, you can try using
|
||||
// --no-sandbox.
|
||||
args: ['--no-sandbox'],
|
||||
})
|
||||
|
||||
const page = await browser.newPage()
|
||||
page.on('console', obj => {
|
||||
console.log(`[console.${obj.type()}] ${obj.text()}`)
|
||||
})
|
||||
|
||||
page.exposeFunction('testBegin', args => {
|
||||
const config = Object.entries(JSON.parse(args)).map(([k, v]) => `${k}=${v}`).join(', ')
|
||||
console.log(`💬 config: ${config}`)
|
||||
})
|
||||
|
||||
page.exposeFunction('testEnd', args => {
|
||||
if (args === null) console.log(`👍 success`)
|
||||
else {
|
||||
const { test, error } = JSON.parse(args)
|
||||
console.log(`❌ error${test ? ` [${test}]` : ``}: ${error}`)
|
||||
allTestsPassed = false
|
||||
}
|
||||
})
|
||||
|
||||
const testDone = new Promise(resolve => {
|
||||
page.exposeFunction('testDone', resolve)
|
||||
})
|
||||
|
||||
await page.goto(`${serverURL}/scripts/browser/index.html`, { waitUntil: 'domcontentloaded' })
|
||||
await testDone
|
||||
await page.close()
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
catch (e) {
|
||||
allTestsPassed = false
|
||||
console.log(`❌ error: ${e && e.stack || e && e.message || e}`)
|
||||
}
|
||||
|
||||
server.close()
|
||||
|
||||
if (!allTestsPassed) {
|
||||
console.error(`❌ browser test failed`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`✅ browser test passed`)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => setTimeout(() => { throw error }))
|
||||
@@ -0,0 +1 @@
|
||||
../../npm/esbuild-wasm/esbuild.wasm
|
||||
@@ -0,0 +1,417 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<style></style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
async function expectThrownError(fn, err) {
|
||||
try {
|
||||
await fn()
|
||||
throw new Error('Expected an error to be thrown')
|
||||
} catch (e) {
|
||||
assertStrictEqual(e.message, err)
|
||||
}
|
||||
}
|
||||
|
||||
function assertStrictEqual(a, b, message = 'Assertion failed') {
|
||||
if (a !== b) {
|
||||
throw new Error(`${message}:
|
||||
Observed: ${JSON.stringify(a)}
|
||||
Expected: ${JSON.stringify(b)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertSameColorsWithNestingTransform(esbuild, { css, html }) {
|
||||
const crawlColors = node => {
|
||||
const { color } = getComputedStyle(node)
|
||||
const children = Array.from(node.children).map(crawlColors)
|
||||
return children.length ? [color, children] : color
|
||||
}
|
||||
const style = document.querySelector('style')
|
||||
document.body.innerHTML = html
|
||||
style.textContent = css
|
||||
const original = JSON.stringify(crawlColors(document.body))
|
||||
|
||||
// Test minified CSS
|
||||
const minified = await esbuild.transform(css, {
|
||||
loader: 'css',
|
||||
minify: true,
|
||||
})
|
||||
style.textContent = minified.code
|
||||
assertStrictEqual(original, JSON.stringify(crawlColors(document.body)), 'Compare minified CSS')
|
||||
|
||||
// Test lowered CSS
|
||||
const lowered = await esbuild.transform(css, {
|
||||
loader: 'css',
|
||||
supported: { nesting: false },
|
||||
})
|
||||
style.textContent = lowered.code
|
||||
assertStrictEqual(original, JSON.stringify(crawlColors(document.body)), 'Compare lowered CSS')
|
||||
}
|
||||
|
||||
const coreTests = ({ esbuild }) => ({
|
||||
async defaultExport() {
|
||||
assertStrictEqual(typeof esbuild.version, 'string')
|
||||
assertStrictEqual(esbuild.version, esbuild.default.version)
|
||||
assertStrictEqual(esbuild.version, esbuild.default.default.version)
|
||||
assertStrictEqual(esbuild.version, esbuild.default.default.default.version)
|
||||
},
|
||||
|
||||
async transformJS() {
|
||||
const { code } = await esbuild.transform('1+2')
|
||||
assertStrictEqual(code, '1 + 2;\n')
|
||||
},
|
||||
|
||||
async transformTS() {
|
||||
const { code } = await esbuild.transform('1 as any + <any>2', { loader: 'ts' })
|
||||
assertStrictEqual(code, '1 + 2;\n')
|
||||
},
|
||||
|
||||
async transformCSS() {
|
||||
const { code } = await esbuild.transform('div { color: red }', { loader: 'css' })
|
||||
assertStrictEqual(code, 'div {\n color: red;\n}\n')
|
||||
},
|
||||
|
||||
async buildFib() {
|
||||
const fibonacciPlugin = {
|
||||
name: 'fib',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^fib\((\d+)\)/ }, args => {
|
||||
return { path: args.path, namespace: 'fib' }
|
||||
})
|
||||
build.onLoad({ filter: /^fib\((\d+)\)/, namespace: 'fib' }, args => {
|
||||
let match = /^fib\((\d+)\)/.exec(args.path), n = +match[1]
|
||||
let contents = n < 2 ? `export default ${n}` : `
|
||||
import n1 from 'fib(${n - 1}) ${args.path}'
|
||||
import n2 from 'fib(${n - 2}) ${args.path}'
|
||||
export default n1 + n2`
|
||||
return { contents }
|
||||
})
|
||||
},
|
||||
}
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: `
|
||||
import x from 'fib(10)'
|
||||
module.exports = x
|
||||
`,
|
||||
},
|
||||
format: 'cjs',
|
||||
bundle: true,
|
||||
plugins: [fibonacciPlugin],
|
||||
})
|
||||
assertStrictEqual(result.outputFiles.length, 1)
|
||||
assertStrictEqual(result.outputFiles[0].path, '<stdout>')
|
||||
const code = result.outputFiles[0].text
|
||||
const answer = {}
|
||||
new Function('module', code)(answer)
|
||||
assertStrictEqual(answer.exports, 55)
|
||||
},
|
||||
|
||||
async buildRelativeIssue693() {
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: `const x=1`,
|
||||
},
|
||||
write: false,
|
||||
outfile: 'esbuild.js',
|
||||
});
|
||||
assertStrictEqual(result.outputFiles.length, 1)
|
||||
assertStrictEqual(result.outputFiles[0].path, '/esbuild.js')
|
||||
assertStrictEqual(result.outputFiles[0].text, 'const x = 1;\n')
|
||||
},
|
||||
|
||||
async watch() {
|
||||
const context = await esbuild.context({})
|
||||
try {
|
||||
await expectThrownError(context.watch, 'Cannot use the "watch" API in this environment')
|
||||
} finally {
|
||||
context.dispose()
|
||||
}
|
||||
},
|
||||
|
||||
async serve() {
|
||||
const context = await esbuild.context({})
|
||||
try {
|
||||
await expectThrownError(context.serve, 'Cannot use the "serve" API in this environment')
|
||||
} finally {
|
||||
context.dispose()
|
||||
}
|
||||
},
|
||||
|
||||
async esbuildBuildSync() {
|
||||
await expectThrownError(esbuild.buildSync, 'The "buildSync" API only works in node')
|
||||
},
|
||||
|
||||
async esbuildTransformSync() {
|
||||
await expectThrownError(esbuild.transformSync, 'The "transformSync" API only works in node')
|
||||
},
|
||||
})
|
||||
|
||||
function setupForProblemCSS(prefix) {
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/2889
|
||||
const original = `
|
||||
/* Variant 1 */
|
||||
.${prefix}-v1 { --a: ; --b: ; max-width: var(--a) var(--b); }
|
||||
.${prefix}-a { --a: 1px; }
|
||||
.${prefix}-b { --b: 2px; }
|
||||
|
||||
/* Variant 2 */
|
||||
.${prefix}-v2 { max-width: var(--a, ) var(--b, ); }
|
||||
.${prefix}-a { --a: 1px; }
|
||||
.${prefix}-b { --b: 2px; }
|
||||
`
|
||||
const style = document.querySelector('style')
|
||||
const test1a = document.createElement('div')
|
||||
const test1b = document.createElement('div')
|
||||
const test2a = document.createElement('div')
|
||||
const test2b = document.createElement('div')
|
||||
test1a.className = `${prefix}-v1 ${prefix}-a`
|
||||
test1b.className = `${prefix}-v1 ${prefix}-b`
|
||||
test2a.className = `${prefix}-v2 ${prefix}-a`
|
||||
test2b.className = `${prefix}-v2 ${prefix}-b`
|
||||
return [original, css => {
|
||||
style.textContent = css
|
||||
document.body.innerHTML = ''
|
||||
document.body.appendChild(test1a)
|
||||
document.body.appendChild(test1b)
|
||||
document.body.appendChild(test2a)
|
||||
document.body.appendChild(test2b)
|
||||
assertStrictEqual(getComputedStyle(test1a).maxWidth, `1px`)
|
||||
assertStrictEqual(getComputedStyle(test1b).maxWidth, `2px`)
|
||||
assertStrictEqual(getComputedStyle(test2a).maxWidth, `1px`)
|
||||
assertStrictEqual(getComputedStyle(test2b).maxWidth, `2px`)
|
||||
}]
|
||||
}
|
||||
|
||||
const cssTests = ({ esbuild }) => ({
|
||||
async problemCSSOriginal() {
|
||||
const [original, runAsserts] = setupForProblemCSS('original')
|
||||
runAsserts(original)
|
||||
},
|
||||
|
||||
async problemCSSPrettyPrinted() {
|
||||
const [original, runAsserts] = setupForProblemCSS('pretty-print')
|
||||
const { code: prettyPrinted } = await esbuild.transform(original, { loader: 'css' })
|
||||
runAsserts(prettyPrinted)
|
||||
},
|
||||
|
||||
async problemCSSMinified() {
|
||||
const [original, runAsserts] = setupForProblemCSS('pretty-print')
|
||||
const { code: minified } = await esbuild.transform(original, { loader: 'css', minify: true })
|
||||
runAsserts(minified)
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/3877
|
||||
async cssNestingIssue3877() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.a .b:has(> span) {
|
||||
.a & span {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
<span>1</span>
|
||||
<div><span>2</span></div>
|
||||
</div>
|
||||
<div class="b">
|
||||
<div><span>3</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/3877#issuecomment-2631385559
|
||||
async cssNestingIssue3877Comment2631385559() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.a {
|
||||
:has(>&) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="a">a</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/3997
|
||||
async cssNestingIssue3997() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.foo {
|
||||
color: blue;
|
||||
&& { color: red; }
|
||||
& { color: green; }
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="foo">x</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/issues/4005
|
||||
async cssNestingIssue4005() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.foo {
|
||||
:where(& > .bar) {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="foo">
|
||||
<div class="bar">bar</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
// See: https://github.com/evanw/esbuild/pull/4037
|
||||
async cssNestingIssue4037() {
|
||||
await assertSameColorsWithNestingTransform(esbuild, {
|
||||
css: `
|
||||
.parent {
|
||||
> .a,
|
||||
> .b1 > .b2 {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`,
|
||||
html: `
|
||||
<div class="parent">
|
||||
<div class="a">a</div>
|
||||
<div class="b1">
|
||||
<div class="b2">b2</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
async function runTest(test, fn) {
|
||||
try {
|
||||
await fn()
|
||||
} catch (e) {
|
||||
e.test = test
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function loadScript(url) {
|
||||
const tag = document.createElement('script')
|
||||
document.head.appendChild(tag)
|
||||
await new Promise((resolve, reject) => {
|
||||
tag.onload = resolve
|
||||
tag.onerror = () => reject(new Error('Failed to load script: ' + url))
|
||||
tag.src = url
|
||||
})
|
||||
const esbuild = window.esbuild
|
||||
delete window.esbuild
|
||||
return esbuild
|
||||
}
|
||||
|
||||
async function testStart() {
|
||||
let allTestsPassed = true
|
||||
|
||||
if (!window.testBegin) window.testBegin = args => {
|
||||
const config = Object.entries(JSON.parse(args)).map(([k, v]) => `${k}=${v}`).join(', ')
|
||||
console.log(`💬 config: ${config}`)
|
||||
}
|
||||
|
||||
if (!window.testEnd) window.testEnd = args => {
|
||||
if (args === null) console.log(`👍 success`)
|
||||
else {
|
||||
const { test, stack, error } = JSON.parse(args)
|
||||
console.log(`❌ error${test ? ` [${test}]` : ``}: ${error}`)
|
||||
allTestsPassed = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.testDone) window.testDone = error => {
|
||||
console.log(allTestsPassed ? `✅ done` : `❌ done`)
|
||||
}
|
||||
|
||||
// Just run CSS tests through a single configuration, but run each test separately
|
||||
{
|
||||
const url = '/npm/esbuild-wasm/esbuild.wasm?' + Math.random()
|
||||
const initializePromise = import('/npm/esbuild-wasm/esm/browser.js?' + Math.random())
|
||||
.then(esbuild => esbuild.initialize({ wasmURL: new URL(url, location.href) })
|
||||
.then(() => esbuild))
|
||||
const tests = cssTests({ esbuild: await initializePromise.catch(() => null) })
|
||||
for (const test in tests) {
|
||||
try {
|
||||
testBegin(JSON.stringify({ type: 'css', test }))
|
||||
await initializePromise
|
||||
await runTest(test, tests[test])
|
||||
testEnd(null)
|
||||
} catch (e) {
|
||||
testEnd(JSON.stringify({
|
||||
test: e.test || null,
|
||||
stack: e.stack || null,
|
||||
error: (e && e.message || e) + '',
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run all core tests through every configuration, but run all tests together (stop after one failure)
|
||||
for (const esm of [false, true]) {
|
||||
for (const min of [false, true]) {
|
||||
for (const worker of [false, true]) {
|
||||
for (const mime of ['correct', 'incorrect']) {
|
||||
for (const approach of ['string', 'url', 'module']) {
|
||||
try {
|
||||
testBegin(JSON.stringify({ esm, min, worker, mime, approach }))
|
||||
const esbuild = esm
|
||||
? await import('/npm/esbuild-wasm/esm/browser' + (min ? '.min' : '') + '.js?' + Math.random())
|
||||
: await loadScript('/npm/esbuild-wasm/lib/browser' + (min ? '.min' : '') + '.js?' + Math.random())
|
||||
const url = mime === 'correct' ? '/npm/esbuild-wasm/esbuild.wasm' : '/scripts/browser/esbuild.wasm.bagel'
|
||||
const initializePromise = {
|
||||
string: () => esbuild.initialize({ wasmURL: url, worker }),
|
||||
url: () => esbuild.initialize({ wasmURL: new URL(url, location.href), worker }),
|
||||
module: () => fetch(url)
|
||||
.then(r => r.arrayBuffer())
|
||||
.then(bytes => WebAssembly.compile(bytes))
|
||||
.then(module => esbuild.initialize({ wasmModule: module, worker })),
|
||||
}[approach]()
|
||||
await initializePromise
|
||||
const tests = coreTests({ esbuild })
|
||||
const promises = []
|
||||
for (const test in tests) promises.push(runTest(test, tests[test]))
|
||||
await Promise.all(promises)
|
||||
testEnd(null)
|
||||
} catch (e) {
|
||||
testEnd(JSON.stringify({
|
||||
test: e.test || null,
|
||||
stack: e.stack || null,
|
||||
error: (e && e.message || e) + '',
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testDone()
|
||||
}
|
||||
|
||||
testStart()
|
||||
|
||||
</script>
|
||||
+1918
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"puppeteer": "23.11.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<p>
|
||||
This script checks to see what characters need to be escaped in a data URL
|
||||
(in addition to % for percent-encoded hexadecimal escapes) for a browser to
|
||||
parse it correctly. This information is used to implement esbuild's
|
||||
<code>dataurl</code> loader. Here is what your current browser requires:
|
||||
</p>
|
||||
<pre id="result"></pre>
|
||||
<p>
|
||||
The answer that works across Chrome, Firefox, and Safari appears to be:
|
||||
<br>
|
||||
<br>Always percent-encode these values: <code>0x09, 0x0A, 0x0D, 0x23</code>
|
||||
<br>Only percent-encode these values in the trailing position: <code>0x00 to 0x08, 0x0B, 0x0C, 0x0E to 0x20</code>
|
||||
</p>
|
||||
<script>
|
||||
|
||||
function percentEncode(i) {
|
||||
if (i >= 0x80) return encodeURIComponent(String.fromCharCode(i))
|
||||
return '%' + (0x100 | i).toString(16).slice(-2)
|
||||
}
|
||||
|
||||
async function urlDoesDecodeTo(url, to) {
|
||||
return to === await fetch(url).then(r => r.text())
|
||||
}
|
||||
|
||||
async function check() {
|
||||
const shouldEncode = []
|
||||
|
||||
for (let i = 0; i <= 0xFF; i++) {
|
||||
const ch = String.fromCharCode(i)
|
||||
const chPercent = percentEncode(i)
|
||||
|
||||
if (!await urlDoesDecodeTo('data:text/plain,' + chPercent, ch)) {
|
||||
throw new Error('Assertion failed: Cannot decode ' + chPercent)
|
||||
}
|
||||
|
||||
const leading = await urlDoesDecodeTo('data:text/plain,' + ch + 'foo', ch + 'foo')
|
||||
const trailing = await urlDoesDecodeTo('data:text/plain,foo' + ch, 'foo' + ch)
|
||||
const embedded = await urlDoesDecodeTo('data:text/plain,foo' + ch + 'foo', 'foo' + ch + 'foo')
|
||||
|
||||
if (!leading && !trailing && !embedded) {
|
||||
shouldEncode.push('U+' + i.toString(16) + ' (' + ch + ')')
|
||||
} else {
|
||||
if (!leading) shouldEncode.push('U+' + i.toString(16) + ' (' + ch + ') leading')
|
||||
if (!trailing) shouldEncode.push('U+' + i.toString(16) + ' (' + ch + ') trailing')
|
||||
if (!embedded) shouldEncode.push('U+' + i.toString(16) + ' (' + ch + ') embedded')
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('result').textContent = 'shouldEncode = ' + JSON.stringify(shouldEncode, null, 2)
|
||||
}
|
||||
|
||||
check()
|
||||
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,181 @@
|
||||
// To run this, you must first build the Deno package with "make platform-deno"
|
||||
import * as esbuildNative from '../deno/mod.js'
|
||||
import * as esbuildWASM from '../deno/wasm.js'
|
||||
import * as path from 'https://deno.land/std@0.95.0/path/mod.ts'
|
||||
import * as asserts from 'https://deno.land/std@0.95.0/testing/asserts.ts'
|
||||
|
||||
const __dirname = path.dirname(path.fromFileUrl(import.meta.url))
|
||||
const rootTestDir = path.join(__dirname, '.deno-tests')
|
||||
const wasmModule = await WebAssembly.compile(await Deno.readFile(path.join(__dirname, '..', 'deno', 'esbuild.wasm')))
|
||||
|
||||
try {
|
||||
Deno.removeSync(rootTestDir, { recursive: true })
|
||||
} catch {
|
||||
}
|
||||
Deno.mkdirSync(rootTestDir, { recursive: true })
|
||||
|
||||
function test(name, backends, fn) {
|
||||
// Note: Do not try to add a timeout for the tests below. It turns out that
|
||||
// calling "setTimeout" from within "Deno.test" changes how Deno waits for
|
||||
// promises in a way that masks problems that would otherwise occur. We want
|
||||
// test coverage for the way that people will likely be using these API calls.
|
||||
//
|
||||
// Specifically tests that Deno would otherwise have failed with "error:
|
||||
// Promise resolution is still pending but the event loop has already
|
||||
// resolved" were being allowed to pass instead. See this issue for more
|
||||
// information: https://github.com/evanw/esbuild/issues/3682
|
||||
|
||||
for (const backend of backends) {
|
||||
switch (backend) {
|
||||
case 'native':
|
||||
Deno.test(name + '-native', async () => {
|
||||
let testDir = path.join(rootTestDir, name + '-native')
|
||||
await Deno.mkdir(testDir, { recursive: true })
|
||||
try {
|
||||
await fn({ esbuild: esbuildNative, testDir })
|
||||
await Deno.remove(testDir, { recursive: true }).catch(() => null)
|
||||
} finally {
|
||||
await esbuildNative.stop()
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case 'wasm-main':
|
||||
Deno.test(name + '-wasm-main', async () => {
|
||||
let testDir = path.join(rootTestDir, name + '-wasm-main')
|
||||
await esbuildWASM.initialize({ wasmModule, worker: false })
|
||||
await Deno.mkdir(testDir, { recursive: true })
|
||||
try {
|
||||
await fn({ esbuild: esbuildWASM, testDir })
|
||||
await Deno.remove(testDir, { recursive: true }).catch(() => null)
|
||||
} finally {
|
||||
await esbuildWASM.stop()
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case 'wasm-worker':
|
||||
Deno.test(name + '-wasm-worker', async () => {
|
||||
let testDir = path.join(rootTestDir, name + '-wasm-worker')
|
||||
await esbuildWASM.initialize({ wasmModule, worker: true })
|
||||
await Deno.mkdir(testDir, { recursive: true })
|
||||
try {
|
||||
await fn({ esbuild: esbuildWASM, testDir })
|
||||
await Deno.remove(testDir, { recursive: true }).catch(() => null)
|
||||
} finally {
|
||||
await esbuildWASM.stop()
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener("unload", (e) => {
|
||||
try {
|
||||
Deno.removeSync(rootTestDir, { recursive: true })
|
||||
} catch {
|
||||
// root test dir possibly already removed, so ignore
|
||||
}
|
||||
})
|
||||
|
||||
// This test doesn't run in WebAssembly because it requires file system access
|
||||
test("basicBuild", ['native'], async ({ esbuild, testDir }) => {
|
||||
const input = path.join(testDir, 'in.ts')
|
||||
const dep = path.join(testDir, 'dep.ts')
|
||||
const output = path.join(testDir, 'out.ts')
|
||||
await Deno.writeTextFile(input, 'import dep from "./dep.ts"; export default dep === 123')
|
||||
await Deno.writeTextFile(dep, 'export default 123')
|
||||
await esbuild.build({
|
||||
entryPoints: [input],
|
||||
bundle: true,
|
||||
outfile: output,
|
||||
format: 'esm',
|
||||
})
|
||||
const result = await import(path.toFileUrl(output))
|
||||
asserts.assertStrictEquals(result.default, true)
|
||||
})
|
||||
|
||||
test("basicContext", ['native'], async ({ esbuild, testDir }) => {
|
||||
const input = path.join(testDir, 'in.ts')
|
||||
const dep = path.join(testDir, 'dep.ts')
|
||||
const output = path.join(testDir, 'out.ts')
|
||||
await Deno.writeTextFile(input, 'import dep from "./dep.ts"; export default dep === 123')
|
||||
await Deno.writeTextFile(dep, 'export default 123')
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: ['in.ts'],
|
||||
bundle: true,
|
||||
outfile: output,
|
||||
format: 'esm',
|
||||
absWorkingDir: testDir,
|
||||
})
|
||||
const { errors, warnings } = await ctx.rebuild()
|
||||
asserts.assertStrictEquals(errors.length, 0)
|
||||
asserts.assertStrictEquals(warnings.length, 0)
|
||||
await ctx.dispose()
|
||||
const result = await import(path.toFileUrl(output))
|
||||
asserts.assertStrictEquals(result.default, true)
|
||||
})
|
||||
|
||||
test("basicPlugin", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
|
||||
const build = await esbuild.build({
|
||||
entryPoints: ['<entry>'],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
write: false,
|
||||
plugins: [{
|
||||
name: 'plug',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^<.*>$/ }, args => ({ path: args.path, namespace: '<>' }))
|
||||
build.onLoad({ filter: /^<entry>$/ }, () => ({ contents: `import dep from "<dep>"; export default dep === 123` }))
|
||||
build.onLoad({ filter: /^<dep>$/ }, () => ({ contents: `export default 123` }))
|
||||
},
|
||||
}],
|
||||
})
|
||||
const result = await import('data:application/javascript;base64,' + btoa(build.outputFiles[0].text))
|
||||
asserts.assertStrictEquals(result.default, true)
|
||||
})
|
||||
|
||||
test("basicTransform", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
|
||||
const ts = 'let x: number = 1+2'
|
||||
const result = await esbuild.transform(ts, { loader: 'ts' })
|
||||
asserts.assertStrictEquals(result.code, 'let x = 1 + 2;\n')
|
||||
})
|
||||
|
||||
// This test doesn't run in WebAssembly because of a stack overflow
|
||||
test("largeTransform", ['native'], async ({ esbuild }) => {
|
||||
// This should be large enough to be bigger than Deno's write buffer
|
||||
let x = '0'
|
||||
for (let i = 0; i < 1000; i++) x += '+' + i
|
||||
x += ','
|
||||
let y = 'return['
|
||||
for (let i = 0; i < 1000; i++) y += x
|
||||
y += ']'
|
||||
const result = await esbuild.build({
|
||||
stdin: {
|
||||
contents: y,
|
||||
},
|
||||
write: false,
|
||||
minifyWhitespace: true,
|
||||
})
|
||||
asserts.assertStrictEquals(result.outputFiles[0].text, y.slice(0, -2) + '];\n')
|
||||
})
|
||||
|
||||
test("analyzeMetafile", ['native', 'wasm-main', 'wasm-worker'], async ({ esbuild }) => {
|
||||
const result = await esbuild.analyzeMetafile({
|
||||
outputs: {
|
||||
'out.js': {
|
||||
bytes: 4096,
|
||||
inputs: {
|
||||
'in.js': {
|
||||
bytesInOutput: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
asserts.assertStrictEquals(result, `
|
||||
out.js 4.0kb 100.0%
|
||||
└ in.js 1.0kb 25.0%
|
||||
`)
|
||||
})
|
||||
@@ -0,0 +1,402 @@
|
||||
function generateTestCase(assign) {
|
||||
let sideEffectCount = 0
|
||||
let patternCount = 0
|
||||
let limit = 10
|
||||
let depth = 0
|
||||
|
||||
function choice(n) {
|
||||
return Math.random() * n | 0
|
||||
}
|
||||
|
||||
function patternAndValue() {
|
||||
patternCount++
|
||||
switch (choice(3)) {
|
||||
case 0: return array()
|
||||
case 1: return object()
|
||||
case 2: return [assign(), choice(10), choice(10)]
|
||||
}
|
||||
}
|
||||
|
||||
function sideEffect(result) {
|
||||
return `s(${sideEffectCount++}${result ? `, ${result}` : ''})`
|
||||
}
|
||||
|
||||
function indent(open, items, close) {
|
||||
let tab = ' '
|
||||
items = items.map(i => `\n${tab.repeat(depth + 1)}${i}`).join(',')
|
||||
return `${open}${items}\n${tab.repeat(depth)}${close}`
|
||||
}
|
||||
|
||||
function id() {
|
||||
return String.fromCharCode('a'.charCodeAt(0) + choice(3))
|
||||
}
|
||||
|
||||
function array() {
|
||||
let count = 1 + choice(2)
|
||||
let pattern = []
|
||||
let value = []
|
||||
|
||||
depth++
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (patternCount > limit) break
|
||||
let [pat, val, defVal] = patternAndValue()
|
||||
switch (choice(3)) {
|
||||
case 0:
|
||||
pattern.push(pat)
|
||||
value.push(val)
|
||||
break
|
||||
case 1:
|
||||
pattern.push(`${pat} = ${sideEffect(defVal)}`)
|
||||
value.push(val)
|
||||
break
|
||||
case 2:
|
||||
pattern.push(`${pat} = ${sideEffect(defVal)}`)
|
||||
value.push(defVal)
|
||||
break
|
||||
}
|
||||
if (choice(10) < 8) value.push(val)
|
||||
}
|
||||
if (choice(2)) {
|
||||
pattern.push(`...${assign()}`)
|
||||
if (choice(10) < 8) value.push(choice(10))
|
||||
}
|
||||
depth--
|
||||
|
||||
return [
|
||||
indent('[', pattern, ']'),
|
||||
indent('[', value, ']'),
|
||||
'[]',
|
||||
]
|
||||
}
|
||||
|
||||
function object() {
|
||||
let count = 1 + choice(2)
|
||||
let pattern = []
|
||||
let value = []
|
||||
let valKeys = new Set()
|
||||
|
||||
depth++
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (patternCount > limit) break
|
||||
let valKey = id()
|
||||
if (valKeys.has(valKey)) continue
|
||||
valKeys.add(valKey)
|
||||
let patKey = choice() ? valKey : `[${sideEffect(`'${valKey}'`)}]`
|
||||
let [pat, val, defVal] = patternAndValue()
|
||||
switch (choice(3)) {
|
||||
case 0:
|
||||
pattern.push(`${patKey}: ${pat}`)
|
||||
value.push(`${valKey}: ${val}`)
|
||||
break
|
||||
case 1:
|
||||
pattern.push(`${patKey}: ${pat} = ${sideEffect(defVal)}`)
|
||||
value.push(`${valKey}: ${val}`)
|
||||
break
|
||||
case 2:
|
||||
pattern.push(`${patKey}: ${pat} = ${sideEffect(defVal)}`)
|
||||
value.push(`${valKey}: ${defVal}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (choice(2)) {
|
||||
pattern.push(`...${assign()}`)
|
||||
if (choice(10) < 8) value.push(`${id()}: ${choice(10)}`)
|
||||
}
|
||||
depth--
|
||||
|
||||
return [
|
||||
indent('{', pattern, '}'),
|
||||
indent('{', value, '}'),
|
||||
'{}',
|
||||
]
|
||||
}
|
||||
|
||||
return choice(2) ? array() : object()
|
||||
}
|
||||
|
||||
function evaluate(code) {
|
||||
let effectTrace = []
|
||||
let assignTarget = {}
|
||||
let sideEffect = (id, value) => (effectTrace.push(id), value)
|
||||
new Function('a', 's', code)(assignTarget, sideEffect)
|
||||
return JSON.stringify({ assignTarget, effectTrace })
|
||||
}
|
||||
|
||||
function generateTestCases(trials) {
|
||||
let testCases = []
|
||||
|
||||
while (testCases.length < trials) {
|
||||
let ids = []
|
||||
let assignCount = 0
|
||||
let [pattern, value] = generateTestCase(() => {
|
||||
let id = `_${assignCount++}`
|
||||
ids.push(id)
|
||||
return id
|
||||
})
|
||||
try {
|
||||
evaluate(`(${pattern.replace(/_/g, 'a._')} = ${value});`)
|
||||
testCases.push([pattern, value, ids])
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
return testCases
|
||||
}
|
||||
|
||||
function AssignmentOperator([pattern, value]) {
|
||||
let ts = `(${pattern.replace(/_/g, 'a._')} = ${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function NamespaceExport([pattern, value]) {
|
||||
let ts = `namespace a { export const ${`${pattern} = ${value}`} }`
|
||||
let js = `(${pattern.replace(/_/g, 'a._')} = ${value});`
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ConstDeclaration([pattern, value, ids]) {
|
||||
let ts = `const ${pattern} = ${value};${ids.map(id => `\na.${id} = ${id};`).join('')}`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function LetDeclaration([pattern, value, ids]) {
|
||||
let ts = `let ${pattern} = ${value};${ids.map(id => `\na.${id} = ${id};`).join('')}`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function VarDeclaration([pattern, value, ids]) {
|
||||
let ts = `var ${pattern} = ${value};${ids.map(id => `\na.${id} = ${id};`).join('')}`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function TryCatchBinding([pattern, value, ids]) {
|
||||
let ts = `try { throw ${value} } catch (${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function FunctionStatementArguments([pattern, value, ids]) {
|
||||
let ts = `function foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }\nfoo(${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function FunctionExpressionArguments([pattern, value, ids]) {
|
||||
let ts = `(function(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} })(${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ArrowFunctionArguments([pattern, value, ids]) {
|
||||
let ts = `((${pattern}) => { ${ids.map(id => `a.${id} = ${id};`).join('\n')} })(${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ObjectMethodArguments([pattern, value, ids]) {
|
||||
let ts = `({ foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} } }).foo(${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ClassStatementMethodArguments([pattern, value, ids]) {
|
||||
let ts = `class Foo { foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} } }\nnew Foo().foo(${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ClassExpressionMethodArguments([pattern, value, ids]) {
|
||||
let ts = `(new (class { foo(${pattern}) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} } })).foo(${value});`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForLoopConst([pattern, value, ids]) {
|
||||
let ts = `var i; for (const ${pattern} = ${value}; i < 1; i++) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForLoopLet([pattern, value, ids]) {
|
||||
let ts = `for (let ${pattern} = ${value}, i = 0; i < 1; i++) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForLoopVar([pattern, value, ids]) {
|
||||
let ts = `for (var ${pattern} = ${value}, i = 0; i < 1; i++) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForLoop([pattern, value]) {
|
||||
let ts = `for (${pattern.replace(/_/g, 'a._')} = ${value}; 0; ) ;`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForOfLoopConst([pattern, value, ids]) {
|
||||
let ts = `for (const ${pattern} of [${value}]) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForOfLoopLet([pattern, value, ids]) {
|
||||
let ts = `for (let ${pattern} of [${value}]) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForOfLoopVar([pattern, value, ids]) {
|
||||
let ts = `for (var ${pattern} of [${value}]) { ${ids.map(id => `a.${id} = ${id};`).join('\n')} }`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
function ForOfLoop([pattern, value]) {
|
||||
let ts = `for (${pattern.replace(/_/g, 'a._')} of [${value}]) ;`
|
||||
let js = ts
|
||||
return { js, ts }
|
||||
}
|
||||
|
||||
async function verify(test, transform, testCases) {
|
||||
let verbose = process.argv.indexOf('--verbose') >= 0
|
||||
let indent = t => t.replace(/\n/g, '\n ')
|
||||
let newline = false
|
||||
console.log(`${test.name} (${transform.name}):`)
|
||||
|
||||
await concurrentMap(testCases, 20, async (testCase) => {
|
||||
let { js, ts } = test(testCase)
|
||||
let expected
|
||||
try {
|
||||
expected = evaluate(js)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
let transformed
|
||||
try {
|
||||
transformed = await transform(ts)
|
||||
} catch (e) {
|
||||
process.stdout.write('T')
|
||||
newline = true
|
||||
|
||||
if (verbose) {
|
||||
console.log('\n' + '='.repeat(80))
|
||||
console.log(indent(`Original code:\n${ts}`))
|
||||
console.log(indent(`Transform error:\n${e}`))
|
||||
newline = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let actual
|
||||
try {
|
||||
actual = evaluate(transformed)
|
||||
} catch (e) {
|
||||
actual = e + ''
|
||||
}
|
||||
|
||||
if (actual !== expected) {
|
||||
process.stdout.write(actual.indexOf('SyntaxError') >= 0 ? 'S' : 'X')
|
||||
newline = true
|
||||
|
||||
if (verbose) {
|
||||
console.log('\n' + '='.repeat(80))
|
||||
console.log(indent(`Original code:\n${ts}`))
|
||||
console.log(indent(`Transformed code:\n${transformed}`))
|
||||
console.log(indent(`Expected output:\n${expected}`))
|
||||
console.log(indent(`Actual output:\n${actual}`))
|
||||
newline = false
|
||||
}
|
||||
} else {
|
||||
process.stdout.write('-')
|
||||
newline = true
|
||||
}
|
||||
})
|
||||
|
||||
if (newline) process.stdout.write('\n')
|
||||
}
|
||||
|
||||
function concurrentMap(items, batch, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let index = 0
|
||||
let pending = 0
|
||||
let next = () => {
|
||||
if (index === items.length && pending === 0) {
|
||||
resolve()
|
||||
} else if (index < items.length) {
|
||||
let item = items[index++]
|
||||
pending++
|
||||
callback(item).then(() => {
|
||||
pending--
|
||||
next()
|
||||
}, e => {
|
||||
items.length = 0
|
||||
reject(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < batch; i++)next()
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let es = require('./esbuild').installForTests()
|
||||
let esbuild = async (x) => (await es.transform(x, { target: 'es6', loader: 'ts' })).code.trim()
|
||||
|
||||
console.log(`
|
||||
Options:
|
||||
--verbose = Print details for failures
|
||||
|
||||
Legend:
|
||||
- = The test passed
|
||||
X = The test failed
|
||||
T = The transform function itself failed
|
||||
S = The generated code has a syntax error`)
|
||||
|
||||
let tests = [
|
||||
// Bindings
|
||||
ConstDeclaration,
|
||||
LetDeclaration,
|
||||
VarDeclaration,
|
||||
TryCatchBinding,
|
||||
FunctionStatementArguments,
|
||||
FunctionExpressionArguments,
|
||||
ArrowFunctionArguments,
|
||||
ObjectMethodArguments,
|
||||
ClassStatementMethodArguments,
|
||||
ClassExpressionMethodArguments,
|
||||
ForLoopConst,
|
||||
ForLoopLet,
|
||||
ForLoopVar,
|
||||
ForOfLoopConst,
|
||||
ForOfLoopLet,
|
||||
ForOfLoopVar,
|
||||
|
||||
// Destructuring
|
||||
AssignmentOperator,
|
||||
ForLoop,
|
||||
ForOfLoop,
|
||||
|
||||
// TypeScript-specific
|
||||
NamespaceExport,
|
||||
]
|
||||
let transforms = [
|
||||
esbuild,
|
||||
]
|
||||
let testCases = generateTestCases(100)
|
||||
|
||||
for (let transform of transforms) {
|
||||
console.log()
|
||||
for (let test of tests) {
|
||||
await verify(test, transform, testCases)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(e => setTimeout(() => { throw e }))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,361 @@
|
||||
const childProcess = require('child_process')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const os = require('os')
|
||||
|
||||
const repoDir = path.dirname(__dirname)
|
||||
const denoDir = path.join(repoDir, 'deno')
|
||||
const npmDir = path.join(repoDir, 'npm', 'esbuild')
|
||||
const version = fs.readFileSync(path.join(repoDir, 'version.txt'), 'utf8').trim()
|
||||
const nodeTarget = 'node10'; // See: https://nodejs.org/en/about/releases/
|
||||
const denoTarget = 'deno1'; // See: https://docs.deno.com/runtime/fundamentals/stability_and_releases/
|
||||
const umdBrowserTarget = 'es2015'; // Transpiles "async"
|
||||
const esmBrowserTarget = 'es2017'; // Preserves "async"
|
||||
|
||||
const buildNeutralLib = (esbuildPath) => {
|
||||
const libDir = path.join(npmDir, 'lib')
|
||||
const binDir = path.join(npmDir, 'bin')
|
||||
fs.mkdirSync(libDir, { recursive: true })
|
||||
fs.mkdirSync(binDir, { recursive: true })
|
||||
|
||||
// Generate "npm/esbuild/install.js"
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'node-install.ts'),
|
||||
'--outfile=' + path.join(npmDir, 'install.js'),
|
||||
'--bundle',
|
||||
'--target=' + nodeTarget,
|
||||
// Note: https://socket.dev have complained that inlining the version into
|
||||
// the install script messes up some internal scanning that they do by
|
||||
// making it seem like esbuild's install script code changes with every
|
||||
// esbuild release. So now we read it from "package.json" instead.
|
||||
// '--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--external:esbuild',
|
||||
'--platform=node',
|
||||
'--log-level=warning',
|
||||
], { cwd: repoDir })
|
||||
|
||||
// Generate "npm/esbuild/lib/main.js"
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'node.ts'),
|
||||
'--outfile=' + path.join(libDir, 'main.js'),
|
||||
'--bundle',
|
||||
'--target=' + nodeTarget,
|
||||
'--define:WASM=false',
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--external:esbuild',
|
||||
'--platform=node',
|
||||
'--log-level=warning',
|
||||
], { cwd: repoDir })
|
||||
|
||||
// Generate "npm/esbuild/bin/esbuild"
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'node-shim.ts'),
|
||||
'--outfile=' + path.join(binDir, 'esbuild'),
|
||||
'--bundle',
|
||||
'--target=' + nodeTarget,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--external:esbuild',
|
||||
'--platform=node',
|
||||
'--log-level=warning',
|
||||
], { cwd: repoDir })
|
||||
|
||||
// Generate "npm/esbuild/lib/main.d.ts"
|
||||
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'types.ts'), 'utf8')
|
||||
fs.writeFileSync(path.join(libDir, 'main.d.ts'), types_ts)
|
||||
|
||||
// Get supported platforms
|
||||
const platforms = { exports: {} }
|
||||
new Function('module', 'exports', 'require', childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'node-platform.ts'),
|
||||
'--bundle',
|
||||
'--target=' + nodeTarget,
|
||||
'--external:esbuild',
|
||||
'--platform=node',
|
||||
'--log-level=warning',
|
||||
], { cwd: repoDir }))(platforms, platforms.exports, require)
|
||||
const optionalDependencies = Object.fromEntries(Object.values({
|
||||
...platforms.exports.knownWindowsPackages,
|
||||
...platforms.exports.knownUnixlikePackages,
|
||||
...platforms.exports.knownWebAssemblyFallbackPackages,
|
||||
}).sort().map(x => [x, version]))
|
||||
|
||||
// Update "npm/esbuild/package.json"
|
||||
const pjPath = path.join(npmDir, 'package.json')
|
||||
const package_json = JSON.parse(fs.readFileSync(pjPath, 'utf8'))
|
||||
package_json.optionalDependencies = optionalDependencies
|
||||
fs.writeFileSync(pjPath, JSON.stringify(package_json, null, 2) + '\n')
|
||||
}
|
||||
|
||||
async function generateWorkerCode({ esbuildPath, wasm_exec_js, minify, target }) {
|
||||
const input = `
|
||||
let onmessage
|
||||
let globalThis = {}
|
||||
for (let o = self; o; o = Object.getPrototypeOf(o))
|
||||
for (let k of Object.getOwnPropertyNames(o))
|
||||
if (!(k in globalThis))
|
||||
Object.defineProperty(globalThis, k, { get: () => self[k] })
|
||||
${wasm_exec_js.replace(/\bfs\./g, 'globalThis.fs.')}
|
||||
${fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'worker.ts'), 'utf8')}
|
||||
return m => onmessage(m)
|
||||
`
|
||||
const args = [
|
||||
'--loader=ts',
|
||||
'--target=' + target,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
].concat(minify ? ['--minify'] : [])
|
||||
|
||||
// Note: This uses "execFile" because "execFileSync" in node appears to have
|
||||
// a bug. Specifically when using the "input" option of "execFileSync" to
|
||||
// provide stdin, sometimes (~2% of the time?) node writes all of the input
|
||||
// but then doesn't close the stream. The Go side is stuck reading from stdin
|
||||
// within "ioutil.ReadAll(os.Stdin)" so I suspect it's a bug in node, not in
|
||||
// Go. Explicitly calling "stdin.end()" on the node side appears to fix it.
|
||||
const wasmExecAndWorker = (await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.execFile(esbuildPath, args, { cwd: repoDir }, (err, stdout) => {
|
||||
if (err) reject(err)
|
||||
else resolve(stdout)
|
||||
})
|
||||
proc.stdin.write(input)
|
||||
proc.stdin.end()
|
||||
})).toString().trim()
|
||||
|
||||
const commentLines = wasm_exec_js.split('\n')
|
||||
const firstNonComment = commentLines.findIndex(line => !line.startsWith('//'))
|
||||
const commentPrefix = '\n' + commentLines.slice(0, firstNonComment).join('\n') + '\n'
|
||||
if (minify) return `(postMessage=>{${commentPrefix}${wasmExecAndWorker}})`
|
||||
return `((postMessage) => {${(commentPrefix + wasmExecAndWorker).replace(/\n/g, '\n ')}\n })`
|
||||
}
|
||||
|
||||
exports.buildWasmLib = async (esbuildPath) => {
|
||||
// Asynchronously start building the WebAssembly module
|
||||
const npmWasmDir = path.join(repoDir, 'npm', 'esbuild-wasm')
|
||||
const goBuildPromise = new Promise((resolve, reject) => childProcess.execFile('go',
|
||||
[
|
||||
'build',
|
||||
'-o', path.join(npmWasmDir, 'esbuild.wasm'),
|
||||
'-ldflags=-s -w -buildid=', // This removes ~0.14mb of unnecessary WebAssembly code
|
||||
'-buildvcs=false',
|
||||
'-trimpath',
|
||||
path.join(repoDir, 'cmd', 'esbuild'),
|
||||
],
|
||||
{ cwd: repoDir, stdio: 'inherit', env: { ...process.env, GOOS: 'js', GOARCH: 'wasm' } },
|
||||
err => err ? reject(err) : resolve()))
|
||||
|
||||
const libDir = path.join(npmWasmDir, 'lib')
|
||||
const esmDir = path.join(npmWasmDir, 'esm')
|
||||
fs.mkdirSync(libDir, { recursive: true })
|
||||
fs.mkdirSync(esmDir, { recursive: true })
|
||||
|
||||
// Generate "npm/esbuild-wasm/wasm_exec.js"
|
||||
const GOROOT = childProcess.execFileSync('go', ['env', 'GOROOT']).toString().trim()
|
||||
let wasm_exec_js = fs.readFileSync(path.join(GOROOT, 'lib', 'wasm', 'wasm_exec.js'), 'utf8')
|
||||
let wasm_exec_node_js = fs.readFileSync(path.join(GOROOT, 'lib', 'wasm', 'wasm_exec_node.js'), 'utf8')
|
||||
fs.writeFileSync(path.join(npmWasmDir, 'wasm_exec.js'), wasm_exec_js)
|
||||
fs.writeFileSync(path.join(npmWasmDir, 'wasm_exec_node.js'), wasm_exec_node_js)
|
||||
|
||||
// Generate "npm/esbuild-wasm/lib/main.js"
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'node.ts'),
|
||||
'--outfile=' + path.join(libDir, 'main.js'),
|
||||
'--bundle',
|
||||
'--target=' + nodeTarget,
|
||||
'--format=cjs',
|
||||
'--define:WASM=true',
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--external:esbuild',
|
||||
'--platform=node',
|
||||
'--log-level=warning',
|
||||
], { cwd: repoDir })
|
||||
|
||||
// Generate "npm/esbuild-wasm/lib/main.d.ts" and "npm/esbuild-wasm/lib/browser.d.ts"
|
||||
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'types.ts'), 'utf8')
|
||||
fs.writeFileSync(path.join(libDir, 'main.d.ts'), types_ts)
|
||||
fs.writeFileSync(path.join(libDir, 'browser.d.ts'), types_ts)
|
||||
fs.writeFileSync(path.join(esmDir, 'browser.d.ts'), types_ts)
|
||||
|
||||
for (const minify of [false, true]) {
|
||||
const minifyFlags = minify ? ['--minify'] : []
|
||||
const wasmWorkerCodeUMD = await generateWorkerCode({ esbuildPath, wasm_exec_js, minify, target: umdBrowserTarget })
|
||||
const wasmWorkerCodeESM = await generateWorkerCode({ esbuildPath, wasm_exec_js, minify, target: esmBrowserTarget })
|
||||
|
||||
// Generate "npm/esbuild-wasm/lib/browser.*"
|
||||
const umdPrefix = `(module=>{`
|
||||
const umdSuffix = `})(typeof module==="object"?module:{set exports(x){(typeof self!=="undefined"?self:this).esbuild=x}});`
|
||||
const browserCJS = childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'browser.ts'),
|
||||
'--bundle',
|
||||
'--target=' + umdBrowserTarget,
|
||||
'--format=cjs',
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCodeUMD),
|
||||
'--banner:js=' + umdPrefix,
|
||||
'--footer:js=' + umdSuffix,
|
||||
'--log-level=warning',
|
||||
].concat(minifyFlags), { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCodeUMD)
|
||||
fs.writeFileSync(path.join(libDir, minify ? 'browser.min.js' : 'browser.js'), browserCJS)
|
||||
|
||||
// Generate "npm/esbuild-wasm/esm/browser.*"
|
||||
const browserESM = childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'npm', 'browser.ts'),
|
||||
'--bundle',
|
||||
'--target=' + esmBrowserTarget,
|
||||
'--format=esm',
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCodeESM),
|
||||
'--log-level=warning',
|
||||
].concat(minifyFlags), { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCodeESM)
|
||||
fs.writeFileSync(path.join(esmDir, minify ? 'browser.min.js' : 'browser.js'), browserESM)
|
||||
}
|
||||
|
||||
// Join with the asynchronous WebAssembly build
|
||||
await goBuildPromise
|
||||
|
||||
// Also copy this into the WebAssembly shim directories
|
||||
for (const dir of [
|
||||
path.join(repoDir, 'npm', '@esbuild', 'android-arm'),
|
||||
path.join(repoDir, 'npm', '@esbuild', 'android-x64'),
|
||||
path.join(repoDir, 'npm', '@esbuild', 'openharmony-arm64'),
|
||||
]) {
|
||||
fs.mkdirSync(path.join(dir, 'bin'), { recursive: true })
|
||||
fs.writeFileSync(path.join(dir, 'wasm_exec.js'), wasm_exec_js)
|
||||
fs.writeFileSync(path.join(dir, 'wasm_exec_node.js'), wasm_exec_node_js)
|
||||
fs.copyFileSync(path.join(npmWasmDir, 'bin', 'esbuild'), path.join(dir, 'bin', 'esbuild'))
|
||||
fs.copyFileSync(path.join(npmWasmDir, 'esbuild.wasm'), path.join(dir, 'esbuild.wasm'))
|
||||
}
|
||||
}
|
||||
|
||||
const buildDenoLib = async (esbuildPath) => {
|
||||
// Generate "deno/mod.js"
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'deno', 'mod.ts'),
|
||||
'--bundle',
|
||||
'--outfile=' + path.join(denoDir, 'mod.js'),
|
||||
'--target=' + denoTarget,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--platform=neutral',
|
||||
'--log-level=warning',
|
||||
'--banner:js=/// <reference types="./mod.d.ts" />',
|
||||
], { cwd: repoDir })
|
||||
|
||||
// Generate "deno/wasm.js"
|
||||
const GOROOT = childProcess.execFileSync('go', ['env', 'GOROOT']).toString().trim()
|
||||
let wasm_exec_js = fs.readFileSync(path.join(GOROOT, 'lib', 'wasm', 'wasm_exec.js'), 'utf8')
|
||||
const wasmWorkerCode = await generateWorkerCode({ esbuildPath, wasm_exec_js, minify: true, target: denoTarget })
|
||||
const modWASM = childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'deno', 'wasm.ts'),
|
||||
'--bundle',
|
||||
'--target=' + denoTarget,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--define:WEB_WORKER_SOURCE_CODE=' + JSON.stringify(wasmWorkerCode),
|
||||
'--platform=neutral',
|
||||
'--log-level=warning',
|
||||
'--banner:js=/// <reference types="./wasm.d.ts" />',
|
||||
], { cwd: repoDir }).toString().replace('WEB_WORKER_FUNCTION', wasmWorkerCode)
|
||||
fs.writeFileSync(path.join(denoDir, 'wasm.js'), modWASM)
|
||||
|
||||
// Generate "deno/mod.d.ts"
|
||||
const types_ts = fs.readFileSync(path.join(repoDir, 'lib', 'shared', 'types.ts'), 'utf8')
|
||||
fs.writeFileSync(path.join(denoDir, 'mod.d.ts'), types_ts)
|
||||
fs.writeFileSync(path.join(denoDir, 'wasm.d.ts'), types_ts)
|
||||
|
||||
// And copy the WebAssembly file over to the Deno library as well
|
||||
fs.copyFileSync(path.join(repoDir, 'npm', 'esbuild-wasm', 'esbuild.wasm'), path.join(repoDir, 'deno', 'esbuild.wasm'))
|
||||
}
|
||||
|
||||
// Writing a file atomically is important for watch mode tests since we don't
|
||||
// want to read the file after it has been truncated but before the new contents
|
||||
// have been written.
|
||||
exports.writeFileAtomic = (where, contents) => {
|
||||
// Note: Can't use "os.tmpdir()" because that doesn't work on Windows. CI runs
|
||||
// tests on D:\ and the temporary directory is on C:\ or the other way around.
|
||||
// And apparently it's impossible to move files between C:\ and D:\ or something.
|
||||
// So we have to write the file in the same directory as the destination. This is
|
||||
// unfortunate because it will unnecessarily trigger extra watch mode rebuilds.
|
||||
// So we have to make our tests extra robust so they can still work with random
|
||||
// extra rebuilds thrown in.
|
||||
const file = path.join(path.dirname(where), '.esbuild-atomic-file-' + Math.random().toString(36).slice(2))
|
||||
fs.writeFileSync(file, contents)
|
||||
fs.renameSync(file, where)
|
||||
}
|
||||
|
||||
exports.buildBinary = () => {
|
||||
childProcess.execFileSync('go', ['build', '-ldflags=-s -w -buildid=', '-buildvcs=false', '-trimpath', './cmd/esbuild'], { cwd: repoDir, stdio: 'ignore' })
|
||||
return path.join(repoDir, process.platform === 'win32' ? 'esbuild.exe' : 'esbuild')
|
||||
}
|
||||
|
||||
exports.removeRecursiveSync = path => {
|
||||
try {
|
||||
fs.rmSync(path, { recursive: true })
|
||||
} catch (e) {
|
||||
// Removing stuff on Windows is flaky and unreliable. Don't fail tests
|
||||
// on CI if Windows is just being a pain. Common causes of flakes include
|
||||
// random EPERM and ENOTEMPTY errors.
|
||||
//
|
||||
// The general "solution" to this is to try asking Windows to redo the
|
||||
// failing operation repeatedly until eventually giving up after a
|
||||
// timeout. But that doesn't guarantee that flakes will be fixed so we
|
||||
// just give up instead. People that want reasonable file system
|
||||
// behavior on Windows should use WSL instead.
|
||||
}
|
||||
}
|
||||
|
||||
const updateVersionPackageJSON = pathToPackageJSON => {
|
||||
const version = fs.readFileSync(path.join(path.dirname(__dirname), 'version.txt'), 'utf8').trim()
|
||||
const json = JSON.parse(fs.readFileSync(pathToPackageJSON, 'utf8'))
|
||||
|
||||
if (json.version !== version) {
|
||||
json.version = version
|
||||
fs.writeFileSync(pathToPackageJSON, JSON.stringify(json, null, 2) + '\n')
|
||||
}
|
||||
}
|
||||
|
||||
exports.installForTests = () => {
|
||||
// Build the "esbuild" binary and library
|
||||
const esbuildPath = exports.buildBinary()
|
||||
buildNeutralLib(esbuildPath)
|
||||
|
||||
// Install the "esbuild" package to a temporary directory. On Windows, it's
|
||||
// sometimes randomly impossible to delete this installation directory. My
|
||||
// best guess is that this is because the esbuild process is kept alive until
|
||||
// the process exits for "buildSync" and "transformSync", and that sometimes
|
||||
// prevents Windows from deleting the directory it's in. The call in tests to
|
||||
// "rimraf.sync()" appears to hang when this happens. Other operating systems
|
||||
// don't have a problem with this. This has only been a problem on the Windows
|
||||
// VM in GitHub CI. I cannot reproduce this issue myself.
|
||||
const installDir = path.join(os.tmpdir(), 'esbuild-' + Math.random().toString(36).slice(2))
|
||||
const env = { ...process.env, ESBUILD_BINARY_PATH: esbuildPath }
|
||||
fs.mkdirSync(installDir)
|
||||
fs.writeFileSync(path.join(installDir, 'package.json'), '{}')
|
||||
childProcess.execSync(`npm pack --silent "${npmDir}"`, { cwd: installDir, stdio: 'inherit' })
|
||||
childProcess.execSync(`npm install --silent --no-audit --no-optional --ignore-scripts=false --progress=false esbuild-${version}.tgz`, { cwd: installDir, env, stdio: 'inherit' })
|
||||
|
||||
// Evaluate the code
|
||||
const ESBUILD_PACKAGE_PATH = path.join(installDir, 'node_modules', 'esbuild')
|
||||
const mod = require(ESBUILD_PACKAGE_PATH)
|
||||
Object.defineProperty(mod, 'ESBUILD_PACKAGE_PATH', { value: ESBUILD_PACKAGE_PATH })
|
||||
return mod
|
||||
}
|
||||
|
||||
const updateVersionGo = () => {
|
||||
const version_txt = fs.readFileSync(path.join(repoDir, 'version.txt'), 'utf8').trim()
|
||||
const version_go = `package main\n\nconst esbuildVersion = "${version_txt}"\n`
|
||||
const version_go_path = path.join(repoDir, 'cmd', 'esbuild', 'version.go')
|
||||
|
||||
// Update this atomically to avoid issues with this being overwritten during use
|
||||
const temp_path = version_go_path + Math.random().toString(36).slice(1)
|
||||
fs.writeFileSync(temp_path, version_go)
|
||||
fs.renameSync(temp_path, version_go_path)
|
||||
}
|
||||
|
||||
// This is helpful for ES6 modules which don't have access to __dirname
|
||||
exports.dirname = __dirname
|
||||
|
||||
// The main Makefile invokes this script before publishing
|
||||
if (require.main === module) {
|
||||
if (process.argv.indexOf('--wasm') >= 0) exports.buildWasmLib(process.argv[2])
|
||||
else if (process.argv.indexOf('--deno') >= 0) buildDenoLib(process.argv[2])
|
||||
else if (process.argv.indexOf('--version') >= 0) updateVersionPackageJSON(process.argv[2])
|
||||
else if (process.argv.indexOf('--neutral') >= 0) buildNeutralLib(process.argv[2])
|
||||
else if (process.argv.indexOf('--update-version-go') >= 0) updateVersionGo()
|
||||
else throw new Error('Expected a flag')
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// ES5 reference: https://es5.github.io/
|
||||
//
|
||||
// A conforming implementation of this International standard shall interpret
|
||||
// characters in conformance with the Unicode Standard, Version 3.0 or later
|
||||
// and ISO/IEC 10646-1 with either UCS-2 or UTF-16 as the adopted encoding
|
||||
// form, implementation level 3. If the adopted ISO/IEC 10646-1 subset is not
|
||||
// otherwise specified, it is presumed to be the BMP subset, collection 300.
|
||||
//
|
||||
// UnicodeLetter: any character in the Unicode categories “Uppercase letter (Lu)”,
|
||||
// “Lowercase letter (Ll)”, “Titlecase letter (Lt)”, “Modifier letter (Lm)”,
|
||||
// “Other letter (Lo)”, or “Letter number (Nl)”.
|
||||
const idStartES5 = [].concat(
|
||||
require('@unicode/unicode-3.0.0/General_Category/Uppercase_Letter/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Lowercase_Letter/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Titlecase_Letter/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Modifier_Letter/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Other_Letter/code-points'),
|
||||
|
||||
// The "letter number" category is not included because old versions of Safari
|
||||
// had a bug where they didn't include it. This means it does not match ES5.
|
||||
// We need to make sure we escape these characters so Safari can read them.
|
||||
// See https://github.com/evanw/esbuild/issues/1349 for more information.
|
||||
// require('@unicode/unicode-3.0.0/General_Category/Letter_Number/code-points'),
|
||||
).sort((a, b) => a - b)
|
||||
|
||||
// UnicodeCombiningMark: any character in the Unicode categories “Non-spacing mark (Mn)”
|
||||
// or “Combining spacing mark (Mc)”
|
||||
// UnicodeDigit: any character in the Unicode category “Decimal number (Nd)”
|
||||
// UnicodeConnectorPunctuation: any character in the Unicode category “Connector punctuation (Pc)”
|
||||
const idContinueES5 = idStartES5.concat(
|
||||
require('@unicode/unicode-3.0.0/General_Category/Nonspacing_Mark/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Spacing_Mark/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Decimal_Number/code-points'),
|
||||
require('@unicode/unicode-3.0.0/General_Category/Connector_Punctuation/code-points'),
|
||||
).sort((a, b) => a - b)
|
||||
|
||||
// ESNext reference: https://tc39.es/ecma262/
|
||||
//
|
||||
// A conforming implementation of ECMAScript must interpret source text input
|
||||
// in conformance with the Unicode Standard, Version 5.1.0 or later and ISO/IEC
|
||||
// 10646. If the adopted ISO/IEC 10646-1 subset is not otherwise specified, it
|
||||
// is presumed to be the Unicode set, collection 10646.
|
||||
//
|
||||
// UnicodeIDStart: any Unicode code point with the Unicode property “ID_Start”
|
||||
const idStartESNext = require('@unicode/unicode-15.1.0/Binary_Property/ID_Start/code-points')
|
||||
const idStartESNextSet = new Set(idStartESNext)
|
||||
|
||||
// Unicode 4.1 through Unicode 15 omitted these two characters from ID_Continue
|
||||
// by accident. However, this accident was corrected in Unicode 15.1. Any JS VM
|
||||
// that supports ES6+ but that uses a version of Unicode earlier than 15.1 will
|
||||
// consider these to be a syntax error, so we deliberately omit these characters
|
||||
// from the set of identifiers that are valid in both ES5 and ES6+. For more info
|
||||
// see 2.2 in https://www.unicode.org/L2/L2023/23160-utc176-properties-recs.pdf
|
||||
const ID_Continue_mistake = new Set([0x30FB, 0xFF65])
|
||||
|
||||
// UnicodeIDContinue: any Unicode code point with the Unicode property “ID_Continue”
|
||||
const idContinueESNext = require('@unicode/unicode-15.1.0/Binary_Property/ID_Continue/code-points')
|
||||
const idContinueESNextSet = new Set(idContinueESNext)
|
||||
|
||||
// These identifiers are valid in both ES5 and ES6+ (i.e. an intersection of both)
|
||||
const idStartES5AndESNext = idStartES5.filter(n => idStartESNextSet.has(n))
|
||||
const idContinueES5AndESNext = idContinueES5.filter(n => idContinueESNextSet.has(n) && !ID_Continue_mistake.has(n))
|
||||
|
||||
// These identifiers are valid in either ES5 or ES6+ (i.e. a union of both)
|
||||
const idStartES5OrESNext = [...new Set(idStartES5.concat(idStartESNext))].sort((a, b) => a - b)
|
||||
const idContinueES5OrESNext = [...new Set(idContinueES5.concat(idContinueESNext))].sort((a, b) => a - b)
|
||||
|
||||
function generateRangeTable(codePoints) {
|
||||
let lines = []
|
||||
let index = 0
|
||||
let latinOffset = 0
|
||||
|
||||
while (latinOffset < codePoints.length && codePoints[latinOffset] <= 0xFF) {
|
||||
latinOffset++
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`&unicode.RangeTable{`,
|
||||
`\tLatinOffset: ${latinOffset},`,
|
||||
`\tR16: []unicode.Range16{`,
|
||||
)
|
||||
|
||||
// 16-bit code points
|
||||
while (index < codePoints.length && codePoints[index] < 0x1000) {
|
||||
let start = codePoints[index]
|
||||
index++
|
||||
while (index < codePoints.length && codePoints[index] < 0x1000 && codePoints[index] === codePoints[index - 1] + 1) {
|
||||
index++
|
||||
}
|
||||
let end = codePoints[index - 1]
|
||||
lines.push(`\t\t{Lo: 0x${start.toString(16)}, Hi: 0x${end.toString(16)}, Stride: 1},`)
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`\t},`,
|
||||
`\tR32: []unicode.Range32{`,
|
||||
)
|
||||
|
||||
// 32-bit code points
|
||||
while (index < codePoints.length) {
|
||||
let start = codePoints[index]
|
||||
index++
|
||||
while (index < codePoints.length && codePoints[index] === codePoints[index - 1] + 1) {
|
||||
index++
|
||||
}
|
||||
let end = codePoints[index - 1]
|
||||
lines.push(`\t\t{Lo: 0x${start.toString(16)}, Hi: 0x${end.toString(16)}, Stride: 1},`)
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`\t},`,
|
||||
`}`,
|
||||
)
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, '..', 'internal', 'js_ast', 'unicode.go'),
|
||||
`// This file was automatically generated by ${path.basename(__filename)}. Do not edit.
|
||||
package js_ast
|
||||
|
||||
import "unicode"
|
||||
|
||||
var idStartES5AndESNext = ${generateRangeTable(idStartES5AndESNext)}
|
||||
|
||||
var idContinueES5AndESNext = ${generateRangeTable(idContinueES5AndESNext)}
|
||||
|
||||
var idStartES5OrESNext = ${generateRangeTable(idStartES5OrESNext)}
|
||||
|
||||
var idContinueES5OrESNext = ${generateRangeTable(idContinueES5OrESNext)}
|
||||
`)
|
||||
@@ -0,0 +1,95 @@
|
||||
.linear.red_to_green-esbuild {
|
||||
background: linear-gradient(to right, color(display-p3 1 0 0), color(display-p3 0 0.6 0));
|
||||
}
|
||||
|
||||
.radial.red_to_green-esbuild {
|
||||
background: radial-gradient(color(display-p3 1 0 0), color(display-p3 0 0.6 0));
|
||||
}
|
||||
|
||||
.conic.red_to_green-esbuild {
|
||||
background: conic-gradient(color(display-p3 1 0 0), color(display-p3 0 0.6 0));
|
||||
}
|
||||
|
||||
.linear.rainbow_shorter-esbuild {
|
||||
background: linear-gradient(to right in hwb shorter hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_shorter-esbuild {
|
||||
background: radial-gradient(in hwb shorter hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_shorter-esbuild {
|
||||
background: conic-gradient(in hwb shorter hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.rainbow_longer-esbuild {
|
||||
background: linear-gradient(to right in hsl longer hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_longer-esbuild {
|
||||
background: radial-gradient(in hsl longer hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_longer-esbuild {
|
||||
background: conic-gradient(in hsl longer hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.rainbow_increasing-esbuild {
|
||||
background: linear-gradient(to right in lch increasing hue, hsl(240deg 100% 75%) 10%, hsl(180deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_increasing-esbuild {
|
||||
background: radial-gradient(in lch increasing hue, hsl(240deg 100% 75%) 10%, hsl(180deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_increasing-esbuild {
|
||||
background: conic-gradient(in lch increasing hue, hsl(240deg 100% 75%) 10%, hsl(180deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.rainbow_decreasing-esbuild {
|
||||
background: linear-gradient(to right in oklch decreasing hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_decreasing-esbuild {
|
||||
background: radial-gradient(in oklch decreasing hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_decreasing-esbuild {
|
||||
background: conic-gradient(in oklch decreasing hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.midpoint_hint-esbuild {
|
||||
background: linear-gradient(to right, #f00, #ff0, 75%, #0ff, #00f);
|
||||
}
|
||||
|
||||
.radial.midpoint_hint-esbuild {
|
||||
background: radial-gradient(#f00, #ff0, 75%, #0ff, #00f);
|
||||
}
|
||||
|
||||
.conic.midpoint_hint-esbuild {
|
||||
background: conic-gradient(#f00, #ff0, 75%, #0ff, #00f);
|
||||
}
|
||||
|
||||
.linear.premultiplied_alpha-esbuild {
|
||||
background: linear-gradient(to right, #f00f, 10%, #00f1, 90%, #0f0f);
|
||||
}
|
||||
|
||||
.radial.premultiplied_alpha-esbuild {
|
||||
background: radial-gradient(#f00f, 10%, #00f1, 90%, #0f0f);
|
||||
}
|
||||
|
||||
.conic.premultiplied_alpha-esbuild {
|
||||
background: conic-gradient(#f00f, 10%, #00f1, 90%, #0f0f);
|
||||
}
|
||||
|
||||
.linear.mixed_units-esbuild {
|
||||
background: linear-gradient(to right, color(display-p3 0.4 0 1) 30px, color(display-p3 0.96 0.75 0.4) 60%);
|
||||
}
|
||||
|
||||
.radial.mixed_units-esbuild {
|
||||
background: radial-gradient(color(display-p3 0.4 0 1) 30px, color(display-p3 1 0.75 0.4) 60%);
|
||||
}
|
||||
|
||||
.conic.mixed_units-esbuild {
|
||||
background: conic-gradient(color(display-p3 0.4 0 1) 30deg, color(display-p3 1 0.75 0.4) 60%);
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<link rel="stylesheet" href="out.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 30px;
|
||||
font: 16px/20px sans-serif;
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 0 10px 0;
|
||||
font: 14px/20px Monaco, monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 50px 0 0 0;
|
||||
}
|
||||
|
||||
.linear {
|
||||
max-width: 400px;
|
||||
height: 20px;
|
||||
text-indent: 4px;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
text-shadow: 0 0 3px black;
|
||||
background: repeating-linear-gradient(-45deg,
|
||||
rgba(191, 191, 191, 0.4) 0 7px,
|
||||
transparent 7px 14px);
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.radial+.radial {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.radial,
|
||||
.conic {
|
||||
width: 97px;
|
||||
height: 97px;
|
||||
text-align: center;
|
||||
line-height: 97px;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
text-shadow: 0 0 3px black;
|
||||
background: repeating-linear-gradient(-45deg,
|
||||
transparent 0 6.86px,
|
||||
rgba(191, 191, 191, 0.4) 6.86px 13.72px);
|
||||
}
|
||||
|
||||
.checkerboard {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.checkerboard:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
background: url('data:image/svg+xml,<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><rect width="20" height="20" fill="white"/><path d="M0 10V0H10V10H20V20H10V10H0Z" fill="%23EEEEEE"/></svg>');
|
||||
}
|
||||
|
||||
.linear.red_to_green-native {
|
||||
background: linear-gradient(to right, color(display-p3 1 0 0), color(display-p3 0 0.6 0));
|
||||
}
|
||||
|
||||
.radial.red_to_green-native {
|
||||
background: radial-gradient(color(display-p3 1 0 0), color(display-p3 0 0.6 0));
|
||||
}
|
||||
|
||||
.conic.red_to_green-native {
|
||||
background: conic-gradient(color(display-p3 1 0 0), color(display-p3 0 0.6 0));
|
||||
}
|
||||
|
||||
.linear.rainbow_shorter-native {
|
||||
background: linear-gradient(to right in hwb shorter hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_shorter-native {
|
||||
background: radial-gradient(in hwb shorter hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_shorter-native {
|
||||
background: conic-gradient(in hwb shorter hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.rainbow_longer-native {
|
||||
background: linear-gradient(to right in hsl longer hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_longer-native {
|
||||
background: radial-gradient(in hsl longer hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_longer-native {
|
||||
background: conic-gradient(in hsl longer hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.rainbow_increasing-native {
|
||||
background: linear-gradient(to right in lch increasing hue, hsl(240deg 100% 75%) 10%, hsl(180deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_increasing-native {
|
||||
background: radial-gradient(in lch increasing hue, hsl(240deg 100% 75%) 10%, hsl(180deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_increasing-native {
|
||||
background: conic-gradient(in lch increasing hue, hsl(240deg 100% 75%) 10%, hsl(180deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.rainbow_decreasing-native {
|
||||
background: linear-gradient(to right in oklch decreasing hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.radial.rainbow_decreasing-native {
|
||||
background: radial-gradient(in oklch decreasing hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.conic.rainbow_decreasing-native {
|
||||
background: conic-gradient(in oklch decreasing hue, hsl(180deg 100% 75%) 10%, hsl(240deg 100% 75%) 90%);
|
||||
}
|
||||
|
||||
.linear.midpoint_hint-native {
|
||||
background: linear-gradient(to right, #f00, #ff0, 75%, #0ff, #00f);
|
||||
}
|
||||
|
||||
.radial.midpoint_hint-native {
|
||||
background: radial-gradient(#f00, #ff0, 75%, #0ff, #00f);
|
||||
}
|
||||
|
||||
.conic.midpoint_hint-native {
|
||||
background: conic-gradient(#f00, #ff0, 75%, #0ff, #00f);
|
||||
}
|
||||
|
||||
.linear.premultiplied_alpha-native {
|
||||
background: linear-gradient(to right, #f00f, 10%, #00f1, 90%, #0f0f);
|
||||
}
|
||||
|
||||
.radial.premultiplied_alpha-native {
|
||||
background: radial-gradient(#f00f, 10%, #00f1, 90%, #0f0f);
|
||||
}
|
||||
|
||||
.conic.premultiplied_alpha-native {
|
||||
background: conic-gradient(#f00f, 10%, #00f1, 90%, #0f0f);
|
||||
}
|
||||
|
||||
.linear.mixed_units-native {
|
||||
background: linear-gradient(to right, color(display-p3 0.4 0 1) 30px, color(display-p3 1 0.75 0.4) 60%);
|
||||
}
|
||||
|
||||
.radial.mixed_units-native {
|
||||
background: radial-gradient(color(display-p3 0.4 0 1) 30px, color(display-p3 1 0.75 0.4) 60%);
|
||||
}
|
||||
|
||||
.conic.mixed_units-native {
|
||||
background: conic-gradient(color(display-p3 0.4 0 1) 30deg, color(display-p3 1 0.75 0.4) 60%);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>This page is a visual test of esbuild's CSS gradient transformation. Run it like this:</p>
|
||||
<pre>./esbuild --servedir=scripts --outfile=scripts/out.css scripts/gradient-tests.css --target=firefox30</pre>
|
||||
<p>Then open this page as <a
|
||||
href="http://localhost:8000/gradient-tests.html">http://localhost:8000/gradient-tests.html</a>.</p>
|
||||
|
||||
<h2>1. Red to green in P3</h2>
|
||||
<pre>gradient(
|
||||
color(display-p3 1 0 0),
|
||||
color(display-p3 0 0.6 0))</pre>
|
||||
<div class="linear red_to_green-native">native</div>
|
||||
<div class="linear red_to_green-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial red_to_green-native">native</div>
|
||||
<div class="radial red_to_green-esbuild">esbuild</div>
|
||||
<div class="conic red_to_green-native">native</div>
|
||||
<div class="conic red_to_green-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>2. Rainbow using shorter hue</h2>
|
||||
<pre>gradient(
|
||||
in hwb shorter hue,
|
||||
hsl(180deg 100% 75%) 10%,
|
||||
hsl(240deg 100% 75%) 90%)</pre>
|
||||
<div class="linear rainbow_shorter-native">native</div>
|
||||
<div class="linear rainbow_shorter-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial rainbow_shorter-native">native</div>
|
||||
<div class="radial rainbow_shorter-esbuild">esbuild</div>
|
||||
<div class="conic rainbow_shorter-native">native</div>
|
||||
<div class="conic rainbow_shorter-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>3. Rainbow using longer hue</h2>
|
||||
<pre>gradient(
|
||||
in hsl longer hue,
|
||||
hsl(180deg 100% 75%) 10%,
|
||||
hsl(240deg 100% 75%) 90%)</pre>
|
||||
<div class="linear rainbow_longer-native">native</div>
|
||||
<div class="linear rainbow_longer-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial rainbow_longer-native">native</div>
|
||||
<div class="radial rainbow_longer-esbuild">esbuild</div>
|
||||
<div class="conic rainbow_longer-native">native</div>
|
||||
<div class="conic rainbow_longer-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>4. Rainbow using increasing hue</h2>
|
||||
<pre>gradient(
|
||||
in lch increasing hue,
|
||||
hsl(240deg 100% 75%) 10%,
|
||||
hsl(180deg 100% 75%) 90%)</pre>
|
||||
<div class="linear rainbow_increasing-native">native</div>
|
||||
<div class="linear rainbow_increasing-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial rainbow_increasing-native">native</div>
|
||||
<div class="radial rainbow_increasing-esbuild">esbuild</div>
|
||||
<div class="conic rainbow_increasing-native">native</div>
|
||||
<div class="conic rainbow_increasing-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>5. Rainbow using decreasing hue</h2>
|
||||
<pre>gradient(
|
||||
in oklch decreasing hue,
|
||||
hsl(180deg 100% 75%) 10%,
|
||||
hsl(240deg 100% 75%) 90%)</pre>
|
||||
<div class="linear rainbow_decreasing-native">native</div>
|
||||
<div class="linear rainbow_decreasing-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial rainbow_decreasing-native">native</div>
|
||||
<div class="radial rainbow_decreasing-esbuild">esbuild</div>
|
||||
<div class="conic rainbow_decreasing-native">native</div>
|
||||
<div class="conic rainbow_decreasing-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>6. Transition hint / midpoint</h2>
|
||||
<pre>gradient(#f00, #ff0, 75%, #0ff, #00f)</pre>
|
||||
<div class="linear midpoint_hint-native">native</div>
|
||||
<div class="linear midpoint_hint-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial midpoint_hint-native">native</div>
|
||||
<div class="radial midpoint_hint-esbuild">esbuild</div>
|
||||
<div class="conic midpoint_hint-native">native</div>
|
||||
<div class="conic midpoint_hint-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>7. Premultiplied alpha</h2>
|
||||
<pre>gradient(#f00f, 10%, #00f1, 90%, #0f0f)</pre>
|
||||
<div class="checkerboard linear premultiplied_alpha-native">native</div>
|
||||
<div class="checkerboard linear premultiplied_alpha-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="checkerboard radial premultiplied_alpha-native">native</div>
|
||||
<div class="checkerboard radial premultiplied_alpha-esbuild">esbuild</div>
|
||||
<div class="checkerboard conic premultiplied_alpha-native">native</div>
|
||||
<div class="checkerboard conic premultiplied_alpha-esbuild">esbuild</div>
|
||||
</section>
|
||||
|
||||
<h2>8. Mixed units</h2>
|
||||
<pre>gradient(
|
||||
color(display-p3 0.4 0 1) 30px,
|
||||
color(display-p3 1 0.75 0.4) 60%)</pre>
|
||||
<div class="linear mixed_units-native">native</div>
|
||||
<div class="linear mixed_units-esbuild">esbuild</div>
|
||||
<section>
|
||||
<div class="radial mixed_units-native">native</div>
|
||||
<div class="radial mixed_units-esbuild">esbuild</div>
|
||||
<div class="conic mixed_units-native">native</div>
|
||||
<div class="conic mixed_units-esbuild">esbuild</div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,475 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Graph debugger</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font: 14px/20px sans-serif;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #222;
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#instructions {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="instructions">
|
||||
This is a debugger for some of esbuild's internals. To use:
|
||||
<ol>
|
||||
<li>Set "debugVerboseMetafile = true"</li>
|
||||
<li>Serve the esbuild repo over localhost</li>
|
||||
<li>Visit this page with "#metafile=path/to/metafile.json"</li>
|
||||
</ol>
|
||||
</div>
|
||||
<canvas></canvas>
|
||||
<script type="module">
|
||||
|
||||
const lightColors = {
|
||||
bg: '#fff',
|
||||
fg: '#000',
|
||||
accent: '#7BF',
|
||||
}
|
||||
|
||||
const darkColors = {
|
||||
bg: '#222',
|
||||
fg: '#ddd',
|
||||
accent: '#FB0',
|
||||
}
|
||||
|
||||
const prefersColorSchemeDark = matchMedia("(prefers-color-scheme: dark)")
|
||||
|
||||
function lightOrDarkColors() {
|
||||
return prefersColorSchemeDark.matches ? darkColors : lightColors
|
||||
}
|
||||
|
||||
const paddingX = 10
|
||||
const paddingY = 10
|
||||
|
||||
class InputFile {
|
||||
constructor(source, data) {
|
||||
this.source = source
|
||||
this.data = data
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.w = 0
|
||||
this.h = 0
|
||||
this.measure()
|
||||
}
|
||||
|
||||
measure() {
|
||||
this.w = 0
|
||||
this.h = 0
|
||||
|
||||
// Title
|
||||
c.font = titleFont
|
||||
this.w = Math.max(this.w, c.measureText(this.source).width)
|
||||
this.h += titleLineHeight
|
||||
|
||||
// Parts
|
||||
let prevIndent = 0
|
||||
c.font = codeFont
|
||||
for (const part of this.data.parts) {
|
||||
let code = part.code.replace(/\t/g, ' ')
|
||||
const lastNewline = code.lastIndexOf('\n')
|
||||
let nextIndent = 0
|
||||
if (lastNewline >= 0) {
|
||||
const lastLine = code.slice(lastNewline + 1)
|
||||
nextIndent = lastLine.length
|
||||
if (!/\S/.test(lastLine)) code = code.slice(0, lastNewline)
|
||||
}
|
||||
code = ' '.repeat(prevIndent) + code
|
||||
prevIndent = nextIndent
|
||||
part.lines = code.split('\n')
|
||||
part.y = this.h
|
||||
for (const line of part.lines) {
|
||||
this.w = Math.max(this.w, c.measureText(line).width)
|
||||
this.h += codeLineHeight
|
||||
}
|
||||
if (part.nsExportPartIndex) {
|
||||
this.w = Math.max(this.w, c.measureText('/* <nsExportPartIndex> */').width)
|
||||
}
|
||||
if (part.wrapperPartIndex) {
|
||||
this.w = Math.max(this.w, c.measureText('/* <wrapperPartIndex> */').width)
|
||||
}
|
||||
part.h = this.h - part.y
|
||||
}
|
||||
|
||||
this.w += paddingX * 2
|
||||
this.h += paddingY * 2
|
||||
}
|
||||
|
||||
render() {
|
||||
const colors = lightOrDarkColors()
|
||||
|
||||
// Background
|
||||
c.clearRect(this.x, this.y, this.w, this.h)
|
||||
|
||||
// Title
|
||||
c.font = titleFont
|
||||
c.textBaseline = 'middle'
|
||||
c.fillStyle = colors.fg
|
||||
c.fillText(this.source, this.x + paddingX, this.y + paddingY + titleLineHeight / 2)
|
||||
|
||||
// Lines
|
||||
c.font = codeFont
|
||||
c.textBaseline = 'middle'
|
||||
c.fillStyle = colors.fg
|
||||
for (const part of this.data.parts) {
|
||||
c.globalAlpha = part.isLive ? 1 : 0.2
|
||||
for (let i = 0; i < part.lines.length; i++) {
|
||||
c.fillText(part.lines[i], this.x + paddingX, this.y + paddingY + part.y + i * codeLineHeight + codeLineHeight / 2)
|
||||
}
|
||||
if (part.nsExportPartIndex) {
|
||||
c.fillText('/* <nsExportPartIndex> */', this.x + paddingX, this.y + paddingY + part.y + codeLineHeight / 2)
|
||||
}
|
||||
if (part.wrapperPartIndex) {
|
||||
c.fillText('/* <wrapperPartIndex> */', this.x + paddingX, this.y + paddingY + part.y + codeLineHeight / 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Border
|
||||
c.globalAlpha = 0.2
|
||||
c.strokeStyle = colors.fg
|
||||
c.strokeRect(this.x, this.y, this.w, this.h)
|
||||
c.globalAlpha = 1
|
||||
}
|
||||
|
||||
renderHover() {
|
||||
const colors = lightOrDarkColors()
|
||||
|
||||
if (this.hoveredPart === -1) {
|
||||
c.fillStyle = colors.accent
|
||||
c.globalAlpha = 0.2
|
||||
c.fillRect(this.x, this.y + paddingY, this.w, titleLineHeight)
|
||||
c.globalAlpha = 1
|
||||
c.fillRect(this.x, this.y + paddingY, 4, titleLineHeight)
|
||||
|
||||
c.strokeStyle = colors.fg
|
||||
c.fillStyle = colors.fg
|
||||
for (const part of this.data.parts) {
|
||||
if (part.canBeRemovedIfUnused) continue
|
||||
drawArrow(
|
||||
this.x, this.y + paddingY + titleLineHeight / 2, -1,
|
||||
this.x, this.y + paddingY + part.y + codeLineHeight / 2, -1,
|
||||
)
|
||||
}
|
||||
} else if (this.hoveredPart !== null) {
|
||||
const part = this.data.parts[this.hoveredPart]
|
||||
c.fillStyle = colors.accent
|
||||
c.globalAlpha = 0.2
|
||||
c.fillRect(this.x, this.y + paddingY + part.y, this.w, part.h)
|
||||
c.globalAlpha = 1
|
||||
c.fillRect(this.x, this.y + paddingY + part.y, 4, part.h)
|
||||
|
||||
c.strokeStyle = colors.fg
|
||||
c.fillStyle = colors.fg
|
||||
drawArrow(
|
||||
this.x, this.y + paddingY + part.y + codeLineHeight / 2, -1,
|
||||
this.x, this.y + paddingY + titleLineHeight / 2, -1,
|
||||
)
|
||||
|
||||
for (const dep of part.dependencies) {
|
||||
if (dep.source === this.source) {
|
||||
const otherPart = this.data.parts[dep.partIndex]
|
||||
drawArrow(
|
||||
this.x, this.y + paddingY + part.y + codeLineHeight / 2, -1,
|
||||
this.x, this.y + paddingY + otherPart.y + codeLineHeight / 2, -1,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
const otherFile = inputFiles.find(file => file.source === dep.source)
|
||||
if (!otherFile) continue
|
||||
const otherPart = otherFile.data.parts[dep.partIndex]
|
||||
drawArrow(
|
||||
this.x + this.w, this.y + paddingY + part.y + codeLineHeight / 2, 1,
|
||||
otherFile.x, otherFile.y + paddingY + otherPart.y + codeLineHeight / 2, -1,
|
||||
)
|
||||
}
|
||||
|
||||
for (const record of part.importRecords) {
|
||||
const otherFile = inputFiles.find(file => file.source === record.source)
|
||||
if (!otherFile) continue
|
||||
drawArrow(
|
||||
this.x + this.w, this.y + paddingY + part.y + codeLineHeight / 2, 1,
|
||||
otherFile.x, otherFile.y + paddingY + titleLineHeight / 2, -1,
|
||||
)
|
||||
}
|
||||
|
||||
let lines = []
|
||||
lines.push(`isLive: ${part.isLive}`)
|
||||
if (part.declaredSymbols.length > 0) {
|
||||
lines.push(`declaredSymbols:`)
|
||||
for (const declSym of part.declaredSymbols) {
|
||||
lines.push(` ${declSym.name}`)
|
||||
}
|
||||
}
|
||||
if (part.symbolUses.length > 0) {
|
||||
lines.push(`symbolUses:`)
|
||||
for (const use of part.symbolUses) {
|
||||
lines.push(` ${use.name} ${use.countEstimate}x`)
|
||||
}
|
||||
}
|
||||
if (part.importRecords.length > 0) {
|
||||
lines.push(`importRecords:`)
|
||||
for (const record of part.importRecords) {
|
||||
lines.push(` ${record.source}`)
|
||||
}
|
||||
}
|
||||
|
||||
c.font = normalFont
|
||||
c.textBaseline = 'middle'
|
||||
c.fillStyle = colors.fg
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
c.fillText(lines[i], this.x + 10, this.y + this.h + 10 + i * normalLineHeight + normalLineHeight / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hoveredPart = null
|
||||
|
||||
onHover(mouseX, mouseY) {
|
||||
this.hoveredPart = null
|
||||
|
||||
if (mouseX !== null && mouseY !== null &&
|
||||
mouseX >= this.x && mouseX < this.x + this.w &&
|
||||
mouseY >= this.y && mouseY < this.y + this.h) {
|
||||
let y = mouseY - this.y - paddingY
|
||||
|
||||
if (y >= 0 && y < titleLineHeight) {
|
||||
this.hoveredPart = -1
|
||||
return true
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.data.parts.length; i++) {
|
||||
const part = this.data.parts[i]
|
||||
if (y >= part.y && y < part.y + part.h) {
|
||||
this.hoveredPart = i
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(mouseX, mouseY) {
|
||||
if (mouseX >= this.x && mouseX < this.x + this.w &&
|
||||
mouseY >= this.y && mouseY < this.y + this.h) {
|
||||
document.body.style.cursor = 'move'
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
oldX = 0
|
||||
oldY = 0
|
||||
|
||||
onMouseDown(mouseX, mouseY) {
|
||||
if (mouseX >= this.x && mouseX < this.x + this.w &&
|
||||
mouseY >= this.y && mouseY < this.y + this.h) {
|
||||
this.oldX = mouseX
|
||||
this.oldY = mouseY
|
||||
document.body.style.cursor = 'move'
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
onMouseDrag(mouseX, mouseY) {
|
||||
this.x += mouseX - this.oldX
|
||||
this.y += mouseY - this.oldY
|
||||
this.oldX = mouseX
|
||||
this.oldY = mouseY
|
||||
document.body.style.cursor = 'move'
|
||||
}
|
||||
|
||||
onMouseUp(e) {
|
||||
}
|
||||
}
|
||||
|
||||
function drawArrow(ax, ay, adx, bx, by, bdx) {
|
||||
let dx = bx - ax
|
||||
let dy = by - ay
|
||||
let d = Math.sqrt(dx * dx + dy * dy)
|
||||
let scale = d / 2
|
||||
c.beginPath()
|
||||
c.moveTo(ax, ay)
|
||||
c.bezierCurveTo(
|
||||
ax + adx * (10 + scale), ay,
|
||||
bx + bdx * 10 + bdx * scale, by,
|
||||
bx + bdx * 10, by,
|
||||
)
|
||||
c.stroke()
|
||||
c.beginPath()
|
||||
c.moveTo(bx, by)
|
||||
c.lineTo(bx + bdx * 10, by - 5)
|
||||
c.lineTo(bx + bdx * 10, by + 5)
|
||||
c.fill()
|
||||
}
|
||||
|
||||
const canvas = document.querySelector('canvas')
|
||||
const c = canvas.getContext('2d')
|
||||
const titleFont = '20px sans-serif'
|
||||
const titleLineHeight = 30
|
||||
const codeFont = '12px monospace'
|
||||
const codeLineHeight = 18
|
||||
const normalFont = '14px sans-serif'
|
||||
const normalLineHeight = 18
|
||||
let width = 0, height = 0
|
||||
let scrollX = 0, scrollY = 0
|
||||
|
||||
let metafile
|
||||
const instructions = document.getElementById('instructions')
|
||||
try {
|
||||
if (!location.hash.startsWith('#metafile=')) throw new Error('Expected "#metafile=" in URL')
|
||||
metafile = await fetch(location.hash.slice('#metafile='.length)).then(r => r.json())
|
||||
} catch (err) {
|
||||
const error = document.createElement('div')
|
||||
error.style.color = 'red'
|
||||
error.textContent = err
|
||||
instructions.append(error)
|
||||
throw err
|
||||
}
|
||||
instructions.remove()
|
||||
|
||||
const outputSource = Object.keys(metafile.outputs)[0]
|
||||
const output = metafile.outputs[outputSource]
|
||||
const inputFiles = Object.entries(output.inputs).map(([source, data]) => new InputFile(source, data)).reverse()
|
||||
|
||||
for (let i = 0; i < inputFiles.length; i++) {
|
||||
const file = inputFiles[i]
|
||||
file.y = 100
|
||||
if (i === 0) {
|
||||
file.x = 100
|
||||
} else {
|
||||
const prevFile = inputFiles[i - 1]
|
||||
file.x = prevFile.x + prevFile.w + 100
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
const colors = lightOrDarkColors()
|
||||
|
||||
width = innerWidth
|
||||
height = innerHeight
|
||||
const ratio = devicePixelRatio
|
||||
canvas.width = Math.round(width * ratio)
|
||||
canvas.height = Math.round(height * ratio)
|
||||
canvas.style.background = colors.bg
|
||||
c.scale(ratio, ratio)
|
||||
|
||||
// Title
|
||||
c.font = titleFont
|
||||
c.textBaseline = 'top'
|
||||
c.fillStyle = colors.fg
|
||||
c.fillText(outputSource, 10, 10)
|
||||
|
||||
// Content
|
||||
c.translate(-scrollX, -scrollY)
|
||||
|
||||
// Inputs
|
||||
for (let i = inputFiles.length - 1; i >= 0; i--) inputFiles[i].render()
|
||||
for (let i = inputFiles.length - 1; i >= 0; i--) inputFiles[i].renderHover()
|
||||
}
|
||||
|
||||
addEventListener('wheel', e => {
|
||||
e.preventDefault()
|
||||
if (e.ctrlKey) return
|
||||
scrollX += e.deltaX
|
||||
scrollY += e.deltaY
|
||||
}, { passive: false })
|
||||
|
||||
let draggingFile = null
|
||||
let isDragging = false
|
||||
|
||||
onmousemove = e => {
|
||||
let mouseX = e.pageX + scrollX
|
||||
let mouseY = e.pageY + scrollY
|
||||
document.body.style.cursor = 'default'
|
||||
if (isDragging) {
|
||||
if (draggingFile) {
|
||||
draggingFile.onMouseDrag(mouseX, mouseY)
|
||||
}
|
||||
} else {
|
||||
for (const file of inputFiles) {
|
||||
if (file.onMouseMove(mouseX, mouseY)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
onhover(mouseX, mouseY)
|
||||
}
|
||||
}
|
||||
|
||||
onmousedown = e => {
|
||||
let mouseX = e.pageX + scrollX
|
||||
let mouseY = e.pageY + scrollY
|
||||
if (!isDragging) {
|
||||
isDragging = true
|
||||
for (const file of inputFiles) {
|
||||
if (file.onMouseDown(mouseX, mouseY)) {
|
||||
draggingFile = file
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
onhover(mouseX, mouseY)
|
||||
}
|
||||
|
||||
onmouseup = e => {
|
||||
let mouseX = e.pageX + scrollX
|
||||
let mouseY = e.pageY + scrollY
|
||||
if (isDragging) {
|
||||
if (draggingFile) {
|
||||
draggingFile.onMouseUp(mouseX, mouseY)
|
||||
draggingFile = null
|
||||
}
|
||||
isDragging = false
|
||||
}
|
||||
onhover(mouseX, mouseY)
|
||||
}
|
||||
|
||||
function onhover(mouseX, mouseY) {
|
||||
for (const file of inputFiles) {
|
||||
if (file.onHover(mouseX, mouseY)) {
|
||||
mouseX = null
|
||||
mouseY = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onblur = () => {
|
||||
draggingFile = null
|
||||
isDragging = false
|
||||
}
|
||||
|
||||
function tick() {
|
||||
requestAnimationFrame(tick)
|
||||
render()
|
||||
}
|
||||
|
||||
tick()
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
// This test verifies that:
|
||||
// - a running service will not prevent NodeJS to exit if there is no compilation in progress.
|
||||
// - the NodeJS process will continue running if there is a serve() active or a transform or build in progress.
|
||||
|
||||
const assert = require('assert')
|
||||
const { fork } = require('child_process');
|
||||
|
||||
// The tests to run in the child process
|
||||
async function tests() {
|
||||
const esbuild = require('./esbuild').installForTests()
|
||||
|
||||
async function testTransform() {
|
||||
const t1 = await esbuild.transform(`1+2`)
|
||||
const t2 = await esbuild.transform(`1+3`)
|
||||
assert.strictEqual(t1.code, `1 + 2;\n`)
|
||||
assert.strictEqual(t2.code, `1 + 3;\n`)
|
||||
}
|
||||
|
||||
async function testServe() {
|
||||
const context = await esbuild.context({})
|
||||
try {
|
||||
const server = await context.serve({})
|
||||
assert(server.hosts.includes('127.0.0.1'))
|
||||
assert.strictEqual(typeof server.port, 'number')
|
||||
} finally {
|
||||
await context.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
async function testBuild() {
|
||||
const context = await esbuild.context({
|
||||
stdin: { contents: '1+2' },
|
||||
write: false,
|
||||
})
|
||||
try {
|
||||
|
||||
const result = await context.rebuild()
|
||||
assert.deepStrictEqual(result.outputFiles.length, 1);
|
||||
assert.deepStrictEqual(result.outputFiles[0].text, '1 + 2;\n');
|
||||
|
||||
const result2 = await context.rebuild()
|
||||
assert.deepStrictEqual(result2.outputFiles.length, 1);
|
||||
assert.deepStrictEqual(result2.outputFiles[0].text, '1 + 2;\n');
|
||||
} finally {
|
||||
await context.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
async function testWatchAndIncremental() {
|
||||
const context = await esbuild.context({
|
||||
stdin: { contents: '1+2' },
|
||||
write: false,
|
||||
})
|
||||
try {
|
||||
await context.watch()
|
||||
|
||||
const result = await context.rebuild()
|
||||
assert.deepStrictEqual(result.outputFiles.length, 1);
|
||||
assert.deepStrictEqual(result.outputFiles[0].text, '1 + 2;\n');
|
||||
|
||||
} finally {
|
||||
await context.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
await testTransform()
|
||||
await testServe()
|
||||
await testBuild()
|
||||
await testWatchAndIncremental()
|
||||
}
|
||||
|
||||
// Called when this is the child process to run the tests.
|
||||
function runTests() {
|
||||
process.exitCode = 1;
|
||||
tests().then(() => {
|
||||
process.exitCode = 0;
|
||||
}, (error) => {
|
||||
console.error('❌', error)
|
||||
});
|
||||
}
|
||||
|
||||
// A child process need to be started to verify that a running service is not hanging node.
|
||||
function startChildProcess() {
|
||||
const child = fork(__filename, ['__forked__'], { stdio: 'inherit', env: process.env });
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
console.error('❌ node unref test timeout - child_process.unref() broken?')
|
||||
process.exit(1);
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
child.on('error', (error) => {
|
||||
console.error('❌', error);
|
||||
process.exit(1);
|
||||
})
|
||||
|
||||
child.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
if (code) {
|
||||
console.error(`❌ node unref tests failed: child exited with code ${code}`)
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`✅ node unref tests passed`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (process.argv[2] === '__forked__') {
|
||||
runTests();
|
||||
} else {
|
||||
startChildProcess();
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
{
|
||||
"name": "scripts",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@types/node": "14.14.6",
|
||||
"@unicode/unicode-15.1.0": "1.5.2",
|
||||
"@unicode/unicode-3.0.0": "1.0.6",
|
||||
"fuse.js": "3.2.0",
|
||||
"js-yaml": "3.14.0",
|
||||
"react": "17.0.1",
|
||||
"source-map": "0.7.4",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "14.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz",
|
||||
"integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw=="
|
||||
},
|
||||
"node_modules/@unicode/unicode-15.1.0": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@unicode/unicode-15.1.0/-/unicode-15.1.0-1.5.2.tgz",
|
||||
"integrity": "sha512-7PAgnShDr8ziK6XeHB/TUVFboDFEhaQKKyrw55/Kx9o6AQDy1s7dJ9KRpRerW9nrR5qMGUQvOqTXOAek6ZIXkg=="
|
||||
},
|
||||
"node_modules/@unicode/unicode-3.0.0": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@unicode/unicode-3.0.0/-/unicode-3.0.0-1.0.6.tgz",
|
||||
"integrity": "sha512-eHhtHouHRCE3r0Sq4Spp/P6z1S000pEYkimT2d1xRoYrUDBUJ2TXbIfn96DgdslhBDtoIrnq2kdsCHWnh5RmBg=="
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/fuse.js": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz",
|
||||
"integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
|
||||
"integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz",
|
||||
"integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw=="
|
||||
},
|
||||
"@unicode/unicode-15.1.0": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@unicode/unicode-15.1.0/-/unicode-15.1.0-1.5.2.tgz",
|
||||
"integrity": "sha512-7PAgnShDr8ziK6XeHB/TUVFboDFEhaQKKyrw55/Kx9o6AQDy1s7dJ9KRpRerW9nrR5qMGUQvOqTXOAek6ZIXkg=="
|
||||
},
|
||||
"@unicode/unicode-3.0.0": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@unicode/unicode-3.0.0/-/unicode-3.0.0-1.0.6.tgz",
|
||||
"integrity": "sha512-eHhtHouHRCE3r0Sq4Spp/P6z1S000pEYkimT2d1xRoYrUDBUJ2TXbIfn96DgdslhBDtoIrnq2kdsCHWnh5RmBg=="
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"fuse.js": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz",
|
||||
"integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"react": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
|
||||
"integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@types/node": "14.14.6",
|
||||
"@unicode/unicode-15.1.0": "1.5.2",
|
||||
"@unicode/unicode-3.0.0": "1.0.6",
|
||||
"fuse.js": "3.2.0",
|
||||
"js-yaml": "3.14.0",
|
||||
"react": "17.0.1",
|
||||
"source-map": "0.7.4",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// This script parses all .ts and .tsx files in the current directory using
|
||||
// esbuild. This is useful to check for parser bugs and/or crashes in esbuild.
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const ts = require('typescript');
|
||||
const child_process = require('child_process');
|
||||
const esbuildPath = path.join(path.dirname(__dirname), 'esbuild');
|
||||
|
||||
function walkDir(root, cb) {
|
||||
for (const entry of fs.readdirSync(root)) {
|
||||
const absolute = path.join(root, entry);
|
||||
if (fs.statSync(absolute).isDirectory()) {
|
||||
walkDir(absolute, cb);
|
||||
} else if ((entry.endsWith('.ts') && !entry.endsWith('.d.ts')) || entry.endsWith('.tsx')) {
|
||||
cb(absolute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
child_process.execSync('make', { cwd: path.dirname(__dirname) });
|
||||
|
||||
// Doing one file at a time is useful for debugging crashes
|
||||
if (process.argv.includes('--individual')) {
|
||||
walkDir(process.cwd(), absolute => {
|
||||
let output = child_process.spawnSync(esbuildPath, [absolute, '--outfile=/dev/null'], { stdio: ['inherit', 'pipe', 'pipe'] });
|
||||
if (output.status) {
|
||||
let result;
|
||||
try {
|
||||
result = ts.transpileModule(fs.readFileSync(absolute, 'utf8'), { reportDiagnostics: true });
|
||||
} catch (e) {
|
||||
// Ignore this file if the TypeScript compiler crashes on it
|
||||
return
|
||||
}
|
||||
if (result.diagnostics.length > 0) {
|
||||
// Ignore this file if the TypeScript compiler has parse errors
|
||||
return
|
||||
}
|
||||
console.log('-'.repeat(80));
|
||||
console.log('Failure:', absolute);
|
||||
console.log('-'.repeat(20) + ' esbuild output:');
|
||||
console.log(output.stdout + output.stderr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise it's much faster to do everything at once
|
||||
else {
|
||||
const tempDir = path.join(os.tmpdir(), 'esbuild-parse-ts-files');
|
||||
try {
|
||||
fs.mkdirSync(tempDir);
|
||||
} catch (e) {
|
||||
}
|
||||
const all = [];
|
||||
walkDir(process.cwd(), absolute => all.push(absolute));
|
||||
try {
|
||||
child_process.execFileSync(esbuildPath, ['--outdir=' + tempDir].concat(all), { stdio: 'inherit' });
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
const { installForTests, removeRecursiveSync } = require('./esbuild')
|
||||
const child_process = require('child_process')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const assert = require('assert')
|
||||
const esbuild = installForTests()
|
||||
|
||||
// Create a fresh test directory
|
||||
const rootTestDir = path.join(__dirname, '.register-test')
|
||||
removeRecursiveSync(rootTestDir)
|
||||
fs.mkdirSync(rootTestDir)
|
||||
|
||||
const entry = path.join(rootTestDir, 'entry.ts')
|
||||
fs.writeFileSync(entry, `
|
||||
console.log('in entry.ts' as string)
|
||||
require('./other.ts')
|
||||
`)
|
||||
|
||||
const other = path.join(rootTestDir, 'other.ts')
|
||||
fs.writeFileSync(other, `
|
||||
console.log('in other.ts' as string)
|
||||
`)
|
||||
|
||||
const register = path.join(rootTestDir, 'register.js')
|
||||
fs.writeFileSync(register, `
|
||||
const esbuild = require(${JSON.stringify(esbuild.ESBUILD_PACKAGE_PATH)});
|
||||
const fs = require('fs');
|
||||
require.extensions['.ts'] = (mod, filename) => {
|
||||
const ts = fs.readFileSync(filename, 'utf8');
|
||||
const { code } = esbuild.transformSync(ts, { loader: 'ts' });
|
||||
mod._compile(code, filename);
|
||||
};
|
||||
`)
|
||||
|
||||
let tests = {
|
||||
async fromMainThread() {
|
||||
let result = await new Promise((resolve, reject) => child_process.execFile('node', ['-r', register, entry], (err, stdout) => {
|
||||
if (err) reject(err)
|
||||
else resolve(stdout)
|
||||
}))
|
||||
assert.strictEqual(result, `in entry.ts\nin other.ts\n`)
|
||||
},
|
||||
|
||||
async fromChildThread({ testDir }) {
|
||||
const startThread = path.join(testDir, 'startThread.js')
|
||||
fs.writeFileSync(startThread, `
|
||||
const worker_threads = require('worker_threads')
|
||||
if (worker_threads.isMainThread) {
|
||||
console.log('in startThread.js')
|
||||
const worker = new worker_threads.Worker(__filename)
|
||||
worker.postMessage(null)
|
||||
worker.on('message', logs => {
|
||||
for (const log of logs) console.log(log)
|
||||
worker.terminate()
|
||||
})
|
||||
} else {
|
||||
worker_threads.parentPort.on('message', () => {
|
||||
console.log('in worker')
|
||||
let logs = []
|
||||
console.log = x => logs.push(x)
|
||||
require('../entry.ts')
|
||||
worker_threads.parentPort.postMessage(logs)
|
||||
})
|
||||
}
|
||||
`)
|
||||
|
||||
let result = await new Promise((resolve, reject) => child_process.execFile('node', ['-r', register, startThread], (err, stdout) => {
|
||||
if (err) reject(err)
|
||||
else resolve(stdout)
|
||||
}))
|
||||
assert.strictEqual(result, `in startThread.js\nin worker\nin entry.ts\nin other.ts\n`)
|
||||
},
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Time out these tests after 5 minutes. This exists to help debug test hangs in CI.
|
||||
let minutes = 5
|
||||
let timeout = setTimeout(() => {
|
||||
console.error(`❌ register tests timed out after ${minutes} minutes, exiting...`)
|
||||
process.exit(1)
|
||||
}, minutes * 60 * 1000)
|
||||
|
||||
const runTest = async ([name, fn]) => {
|
||||
let testDir = path.join(rootTestDir, name)
|
||||
try {
|
||||
fs.mkdirSync(testDir)
|
||||
await fn({ esbuild, testDir })
|
||||
removeRecursiveSync(testDir)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error(`❌ ${name}: ${e && e.message || e}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Run all tests in serial
|
||||
let allTestsPassed = true
|
||||
for (let test of Object.entries(tests)) {
|
||||
if (!await runTest(test)) {
|
||||
allTestsPassed = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!allTestsPassed) {
|
||||
console.error(`❌ register tests failed`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`✅ register tests passed`)
|
||||
removeRecursiveSync(rootTestDir)
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
main().catch(e => setTimeout(() => { throw e }))
|
||||
@@ -0,0 +1,372 @@
|
||||
const { installForTests } = require('./esbuild');
|
||||
const childProcess = require('child_process');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const repoDir = path.dirname(__dirname);
|
||||
const testDir = path.join(repoDir, 'scripts', '.terser-tests');
|
||||
const terserDir = path.join(repoDir, 'demo', 'terser');
|
||||
let U;
|
||||
|
||||
main().catch(e => setTimeout(() => { throw e }));
|
||||
|
||||
async function main() {
|
||||
// Terser's stdout comparisons fail if this is true since stdout contains
|
||||
// terminal color escape codes
|
||||
process.stdout.isTTY = false;
|
||||
|
||||
// Make sure the tests are installed
|
||||
console.log('Downloading terser...');
|
||||
childProcess.execSync('make demo/terser', { cwd: repoDir, stdio: 'pipe' });
|
||||
U = require(terserDir);
|
||||
|
||||
// Create a fresh test directory
|
||||
childProcess.execSync(`rm -fr "${testDir}"`);
|
||||
fs.mkdirSync(testDir)
|
||||
|
||||
// Start the esbuild service
|
||||
const esbuild = installForTests();
|
||||
|
||||
// Find test files
|
||||
const compressDir = path.join(terserDir, 'test', 'compress');
|
||||
const files = fs.readdirSync(compressDir).filter(name => name.endsWith('.js'));
|
||||
|
||||
// Run all tests concurrently
|
||||
let passedTotal = 0;
|
||||
let failedTotal = 0;
|
||||
const runTest = file => test_file(esbuild, path.join(compressDir, file))
|
||||
.then(({ passed, failed }) => {
|
||||
passedTotal += passed;
|
||||
failedTotal += failed;
|
||||
});
|
||||
await Promise.all(files.map(runTest));
|
||||
|
||||
// Clean up test output
|
||||
childProcess.execSync(`rm -fr "${testDir}"`);
|
||||
|
||||
console.log(`${failedTotal} failed out of ${passedTotal + failedTotal}`);
|
||||
if (failedTotal) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function test_file(esbuild, file) {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const tests = parse_test(file);
|
||||
const runTest = name => test_case(esbuild, tests[name])
|
||||
.then(() => passed++)
|
||||
.catch(e => {
|
||||
failed++;
|
||||
console.error(`❌ ${file}: ${name}: ${(e && e.message || e).trim()}\n`);
|
||||
pass = false;
|
||||
});
|
||||
await Promise.all(Object.keys(tests).map(runTest));
|
||||
return { passed, failed };
|
||||
}
|
||||
|
||||
// Modified from "terser/demo/test/compress.js"
|
||||
async function test_case(esbuild, test) {
|
||||
const sandbox = require(path.join(terserDir, 'test', 'sandbox'));
|
||||
const log = (format, args) => { throw new Error(tmpl(format, args)); };
|
||||
|
||||
var semver = require(path.join(terserDir, 'node_modules', 'semver'));
|
||||
var output_options = test.beautify || {};
|
||||
|
||||
// Generate the input code
|
||||
if (test.input instanceof U.AST_SimpleStatement
|
||||
&& test.input.body instanceof U.AST_TemplateString) {
|
||||
try {
|
||||
var input = U.parse(test.input.body.segments[0].value);
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
var input_code = make_code(input, output_options);
|
||||
var input_formatted = test.input.body.segments[0].value;
|
||||
} else {
|
||||
var input = as_toplevel(test.input, test.mangle);
|
||||
var input_code = make_code(input, output_options);
|
||||
var input_formatted = make_code(test.input, {
|
||||
ecma: 2015,
|
||||
beautify: true,
|
||||
quote_style: 3,
|
||||
keep_quoted_props: true
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure it's valid
|
||||
try {
|
||||
U.parse(input_code);
|
||||
} catch (ex) {
|
||||
log("!!! Cannot parse input\n---INPUT---\n{input}\n--PARSE ERROR--\n{error}\n\n", {
|
||||
input: input_formatted,
|
||||
error: ex,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pretty-print it
|
||||
var ast = input.to_mozilla_ast();
|
||||
var mozilla_options = {
|
||||
ecma: output_options.ecma,
|
||||
ascii_only: output_options.ascii_only,
|
||||
comments: false,
|
||||
};
|
||||
var ast_as_string = U.AST_Node.from_mozilla_ast(ast).print_to_string(mozilla_options);
|
||||
|
||||
// Run esbuild as a minifier
|
||||
try {
|
||||
var { code: output } = await esbuild.transform(ast_as_string, {
|
||||
minify: true,
|
||||
keepNames: test.options.keep_fnames,
|
||||
});
|
||||
} catch (e) {
|
||||
const formatError = ({ text, location }) => {
|
||||
if (!location) return `\nerror: ${text}`;
|
||||
const { file, line, column } = location;
|
||||
return `\n${file}:${line}:${column}: ERROR: ${text}`;
|
||||
}
|
||||
log("!!! esbuild failed\n---INPUT---\n{input}\n---ERROR---\n{error}\n", {
|
||||
input: ast_as_string,
|
||||
error: (e && e.message || e) + '' + (e.errors ? e.errors.map(formatError) : ''),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure esbuild generates valid JavaScript
|
||||
try {
|
||||
U.parse(output);
|
||||
} catch (ex) {
|
||||
log("!!! Test matched expected result but cannot parse output\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--REPARSE ERROR--\n{error}\n\n", {
|
||||
input: input_formatted,
|
||||
output: output,
|
||||
error: ex.stack,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the stdout matches our expectations
|
||||
if (test.expect_stdout
|
||||
&& (!test.node_version || semver.satisfies(process.version, test.node_version))
|
||||
&& !process.env.TEST_NO_SANDBOX
|
||||
) {
|
||||
if (test.expect_stdout === true) {
|
||||
test.expect_stdout = sandbox.run_code(input_code, test.prepend_code);
|
||||
}
|
||||
var stdout = sandbox.run_code(output, test.prepend_code);
|
||||
if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
|
||||
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
|
||||
input: input_formatted,
|
||||
output: output,
|
||||
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
|
||||
expected: test.expect_stdout,
|
||||
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
|
||||
actual: stdout,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// The code below was copied verbatim from "terser/demo/test/compress.js"
|
||||
//
|
||||
// UglifyJS is released under the BSD license:
|
||||
//
|
||||
// Copyright 2012-2019 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials
|
||||
// provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
function tmpl() {
|
||||
return U.string_template.apply(this, arguments);
|
||||
}
|
||||
|
||||
function as_toplevel(input, mangle_options) {
|
||||
if (!(input instanceof U.AST_BlockStatement))
|
||||
throw new Error("Unsupported input syntax");
|
||||
for (var i = 0; i < input.body.length; i++) {
|
||||
var stat = input.body[i];
|
||||
if (stat instanceof U.AST_SimpleStatement && stat.body instanceof U.AST_String)
|
||||
input.body[i] = new U.AST_Directive(stat.body);
|
||||
else break;
|
||||
}
|
||||
var toplevel = new U.AST_Toplevel(input);
|
||||
toplevel.figure_out_scope(mangle_options);
|
||||
return toplevel;
|
||||
}
|
||||
|
||||
function parse_test(file) {
|
||||
var script = fs.readFileSync(file, "utf8");
|
||||
// TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS2/issues/348
|
||||
try {
|
||||
var ast = U.parse(script, {
|
||||
filename: file
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Caught error while parsing tests in " + file + "\n");
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
var tests = {};
|
||||
var tw = new U.TreeWalker(function (node, descend) {
|
||||
if (node instanceof U.AST_LabeledStatement
|
||||
&& tw.parent() instanceof U.AST_Toplevel) {
|
||||
var name = node.label.name;
|
||||
if (name in tests) {
|
||||
throw new Error('Duplicated test name "' + name + '" in ' + file);
|
||||
}
|
||||
tests[name] = get_one_test(name, node.body);
|
||||
return true;
|
||||
}
|
||||
if (!(node instanceof U.AST_Toplevel)) croak(node);
|
||||
});
|
||||
ast.walk(tw);
|
||||
return tests;
|
||||
|
||||
function croak(node) {
|
||||
throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", {
|
||||
file: file,
|
||||
line: node.start.line,
|
||||
col: node.start.col,
|
||||
code: make_code(node, { beautify: false })
|
||||
}));
|
||||
}
|
||||
|
||||
function read_boolean(stat) {
|
||||
if (stat.TYPE == "SimpleStatement") {
|
||||
var body = stat.body;
|
||||
if (body instanceof U.AST_Boolean) {
|
||||
return body.value;
|
||||
}
|
||||
}
|
||||
throw new Error("Should be boolean");
|
||||
}
|
||||
|
||||
function read_string(stat) {
|
||||
if (stat.TYPE == "SimpleStatement") {
|
||||
var body = stat.body;
|
||||
switch (body.TYPE) {
|
||||
case "String":
|
||||
return body.value;
|
||||
case "Array":
|
||||
return body.elements.map(function (element) {
|
||||
if (element.TYPE !== "String")
|
||||
throw new Error("Should be array of strings");
|
||||
return element.value;
|
||||
}).join("\n");
|
||||
}
|
||||
}
|
||||
throw new Error("Should be string or array of strings");
|
||||
}
|
||||
|
||||
function get_one_test(name, block) {
|
||||
var test = {
|
||||
name: name,
|
||||
options: {},
|
||||
reminify: true,
|
||||
};
|
||||
var tw = new U.TreeWalker(function (node, descend) {
|
||||
if (node instanceof U.AST_Assign) {
|
||||
if (!(node.left instanceof U.AST_SymbolRef)) {
|
||||
croak(node);
|
||||
}
|
||||
var name = node.left.name;
|
||||
test[name] = evaluate(node.right);
|
||||
return true;
|
||||
}
|
||||
if (node instanceof U.AST_LabeledStatement) {
|
||||
var label = node.label;
|
||||
assert.ok(
|
||||
[
|
||||
"input",
|
||||
"prepend_code",
|
||||
"expect",
|
||||
"expect_error",
|
||||
"expect_exact",
|
||||
"expect_warnings",
|
||||
"expect_stdout",
|
||||
"node_version",
|
||||
"reminify",
|
||||
].includes(label.name),
|
||||
tmpl("Unsupported label {name} [{line},{col}]", {
|
||||
name: label.name,
|
||||
line: label.start.line,
|
||||
col: label.start.col
|
||||
})
|
||||
);
|
||||
var stat = node.body;
|
||||
if (label.name == "expect_exact" || label.name == "node_version") {
|
||||
test[label.name] = read_string(stat);
|
||||
} else if (label.name == "reminify") {
|
||||
var value = read_boolean(stat);
|
||||
test.reminify = value == null || value;
|
||||
} else if (label.name == "expect_stdout") {
|
||||
var body = stat.body;
|
||||
if (body instanceof U.AST_Boolean) {
|
||||
test[label.name] = body.value;
|
||||
} else if (body instanceof U.AST_Call) {
|
||||
var ctor = global[body.expression.name];
|
||||
assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", {
|
||||
line: label.start.line,
|
||||
col: label.start.col
|
||||
}));
|
||||
test[label.name] = ctor.apply(null, body.args.map(function (node) {
|
||||
assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", {
|
||||
line: label.start.line,
|
||||
col: label.start.col
|
||||
}));
|
||||
return node.value;
|
||||
}));
|
||||
} else {
|
||||
test[label.name] = read_string(stat) + "\n";
|
||||
}
|
||||
} else if (label.name === "prepend_code") {
|
||||
test[label.name] = read_string(stat);
|
||||
} else {
|
||||
test[label.name] = stat;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
block.walk(tw);
|
||||
return test;
|
||||
}
|
||||
}
|
||||
|
||||
function make_code(ast, options) {
|
||||
var stream = U.OutputStream(options);
|
||||
ast.print(stream);
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
function evaluate(code) {
|
||||
if (code instanceof U.AST_Node)
|
||||
code = make_code(code, { beautify: true });
|
||||
return new Function("return(" + code + ")")();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
const child_process = require('child_process')
|
||||
const esbuild = require('./esbuild')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const ESBUILD_BINARY_PATH = esbuild.buildBinary()
|
||||
const rootDir = path.join(__dirname, '..', 'require', 'yarnpnp')
|
||||
|
||||
function run(command) {
|
||||
console.log('\n\x1B[37m' + '$ ' + command + '\x1B[0m')
|
||||
child_process.execSync(command, { cwd: rootDir, stdio: 'inherit' })
|
||||
}
|
||||
|
||||
function modTime(file) {
|
||||
return fs.statSync(file).mtimeMs
|
||||
}
|
||||
|
||||
function reinstallYarnIfNeeded() {
|
||||
const yarnPath = path.join(rootDir, '.yarn', 'releases', 'yarn-4.0.0-rc.22.cjs')
|
||||
|
||||
if (fs.existsSync(yarnPath) && modTime(yarnPath) > Math.max(
|
||||
modTime(path.join(rootDir, 'package.json')),
|
||||
modTime(path.join(rootDir, 'yarn.lock')),
|
||||
)) {
|
||||
return
|
||||
}
|
||||
|
||||
fs.rmSync(path.join(rootDir, '.pnp.cjs'), { recursive: true, force: true })
|
||||
fs.rmSync(path.join(rootDir, '.pnp.loader.mjs'), { recursive: true, force: true })
|
||||
fs.rmSync(path.join(rootDir, '.yarn'), { recursive: true, force: true })
|
||||
fs.rmSync(path.join(rootDir, '.yarnrc.yml'), { recursive: true, force: true })
|
||||
|
||||
try {
|
||||
run('yarn set version 4.0.0-rc.22')
|
||||
} catch {
|
||||
run('npm i -g yarn') // Install Yarn globally if it's not already installed
|
||||
run('yarn set version 4.0.0-rc.22')
|
||||
}
|
||||
|
||||
let rc
|
||||
try {
|
||||
rc = fs.readFileSync(path.join(rootDir, '.yarnrc.yml'), 'utf8')
|
||||
} catch {
|
||||
rc = '' // Sometimes this file doesn't exist, so pretend it's empty
|
||||
}
|
||||
fs.writeFileSync(path.join(rootDir, '.yarnrc.yml'), `
|
||||
pnpEnableEsmLoader: true
|
||||
pnpIgnorePatterns: ["./bar/**"]
|
||||
|
||||
# Note: Yarn 4 defaults to "enableGlobalCache: true" which doesn't
|
||||
# work on Windows due to cross-drive issues with relative paths.
|
||||
# Explicitly set "enableGlobalCache: false" to avoid this issue.
|
||||
enableGlobalCache: false
|
||||
|
||||
` + rc)
|
||||
|
||||
run('yarn install')
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
// Make sure the tests are valid
|
||||
run('yarn node in.mjs')
|
||||
|
||||
// Test the native build
|
||||
child_process.execFileSync(ESBUILD_BINARY_PATH, [
|
||||
'in.mjs',
|
||||
'--bundle',
|
||||
'--log-level=debug',
|
||||
'--platform=node',
|
||||
'--outfile=out-native.js',
|
||||
], { cwd: rootDir, stdio: 'inherit' })
|
||||
run('node out-native.js')
|
||||
|
||||
// Test the WebAssembly build
|
||||
esbuild.buildWasmLib(ESBUILD_BINARY_PATH)
|
||||
run('node ../../npm/esbuild-wasm/bin/esbuild in.mjs --bundle --log-level=debug --platform=node --outfile=out-wasm.js')
|
||||
run('node out-wasm.js')
|
||||
|
||||
// Test the WebAssembly build when run through Yarn's file system shim
|
||||
esbuild.buildWasmLib(ESBUILD_BINARY_PATH)
|
||||
run('yarn node ../../npm/esbuild-wasm/bin/esbuild in.mjs --bundle --log-level=debug --platform=node --outfile=out-wasm-yarn.js')
|
||||
run('node out-wasm-yarn.js')
|
||||
}
|
||||
|
||||
const minutes = 10
|
||||
const timeout = setTimeout(() => {
|
||||
console.error(`❌ Yarn PnP tests timed out after ${minutes} minutes`)
|
||||
process.exit(1)
|
||||
}, minutes * 60 * 1000)
|
||||
|
||||
reinstallYarnIfNeeded()
|
||||
runTests()
|
||||
clearTimeout(timeout)
|
||||
@@ -0,0 +1,114 @@
|
||||
// This file runs test262 tests for the async iteration proposal:
|
||||
// https://github.com/tc39/proposal-async-iteration. It's intended
|
||||
// to be informative (to identify failure points) but not to be
|
||||
// run in CI. Not even node/V8 passes all of these tests.
|
||||
|
||||
const esbuild = require('./esbuild').installForTests()
|
||||
const ts = require('./node_modules/typescript')
|
||||
const fs = require('fs')
|
||||
const vm = require('vm')
|
||||
|
||||
const test262Dir = __dirname + '/../demo/test262'
|
||||
const shim = ``
|
||||
+ fs.readFileSync(test262Dir + '/harness/assert.js', 'utf8')
|
||||
+ fs.readFileSync(test262Dir + '/harness/asyncHelpers.js', 'utf8')
|
||||
+ fs.readFileSync(test262Dir + '/harness/compareArray.js', 'utf8')
|
||||
+ fs.readFileSync(test262Dir + '/harness/propertyHelper.js', 'utf8')
|
||||
+ fs.readFileSync(test262Dir + '/harness/sta.js', 'utf8')
|
||||
|
||||
async function main() {
|
||||
const tests = []
|
||||
|
||||
for (const dir of [
|
||||
test262Dir + '/test/language/statements/async-generator',
|
||||
test262Dir + '/test/language/statements/for-await-of',
|
||||
]) {
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
if (name.endsWith('.js')) {
|
||||
tests.push(dir + '/' + name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const test of tests) {
|
||||
const stuck = () => console.log(`❌ ${test}: stuck`)
|
||||
process.on('exit', stuck)
|
||||
|
||||
let js = fs.readFileSync(test, 'utf8')
|
||||
let negative = /negative:/.test(js)
|
||||
|
||||
let flags = /flags:.*/.exec(js)
|
||||
flags = flags && new Set(/flags: \[([^\]]*)\]/.exec(js)[1].split(', '))
|
||||
|
||||
let didFail = false
|
||||
|
||||
const check = async (kind, codePromise) => {
|
||||
let error
|
||||
try {
|
||||
let { code } = await codePromise
|
||||
code = shim + code
|
||||
if (flags && flags.has('onlyStrict')) {
|
||||
code = '"use strict";\n' + code
|
||||
}
|
||||
await new Promise((resolve, reject) => {
|
||||
if (flags && flags.has('async')) {
|
||||
vm.runInContext(code, vm.createContext({ $DONE: err => err ? reject(err) : resolve() }))
|
||||
} else {
|
||||
vm.runInContext(code, vm.createContext({}))
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
if (!error === !negative) {
|
||||
return true
|
||||
}
|
||||
if (!didFail) {
|
||||
didFail = true
|
||||
console.log(`\n❌ \x1B[37m${test}\x1B[0m`)
|
||||
}
|
||||
if (kind === 'native') kind = '\x1B[32m[' + kind + ']\x1B[0m'
|
||||
else if (kind.startsWith('ts')) kind = '\x1B[34m[' + kind + ']\x1B[0m'
|
||||
else kind = '\x1B[33m[' + kind + ']\x1B[0m'
|
||||
console.log(` ${kind} ${(error + '' || 'unexpected success').replace(/\n/g, '\\n')}`)
|
||||
return false
|
||||
}
|
||||
|
||||
const tsTranspile = async (compilerOptions) => {
|
||||
const { outputText } = ts.transpileModule(js, { compilerOptions })
|
||||
return { code: outputText }
|
||||
}
|
||||
|
||||
// See if node's native support checks out (it currently doesn't due to recent specification changes)
|
||||
await check('native', { code: js })
|
||||
|
||||
// Ignore tests that check whether "yield" can be used as a valid
|
||||
// identifier. This obviously doesn't work when we must transform
|
||||
// async functions into generator functions, but that's fine.
|
||||
const hasYieldKeyword = test.includes('yield-ident-valid') || test.includes('array-elem-target-yield-valid')
|
||||
|
||||
// Check "tsc"
|
||||
await check('ts=esnext', tsTranspile({ target: ts.ScriptTarget.ESNext }))
|
||||
await check('ts=es2017', tsTranspile({ target: ts.ScriptTarget.ES2017 }))
|
||||
if (!hasYieldKeyword) {
|
||||
await check('ts=es2016', { code: ts.transpileModule(js, { compilerOptions: { target: ts.ScriptTarget.ES2016 } }).outputText })
|
||||
}
|
||||
|
||||
// Check "esbuild"
|
||||
await check('target=esnext', esbuild.transform(js, { keepNames: true, target: 'esnext' }))
|
||||
await check('target=es2017', esbuild.transform(js, { keepNames: true, target: 'es2017' }))
|
||||
await check('for-await=false', esbuild.transform(js, { keepNames: true, supported: { 'for-await': false } }))
|
||||
if (!hasYieldKeyword) {
|
||||
await check('target=es2016', esbuild.transform(js, { keepNames: true, target: 'es2016' }))
|
||||
await check('async-await=false', esbuild.transform(js, { keepNames: true, supported: { 'async-await': false } }))
|
||||
await check('for-await=false,async-await=false', esbuild.transform(js, { keepNames: true, supported: { 'for-await': false, 'async-await': false } }))
|
||||
}
|
||||
|
||||
process.off('exit', stuck)
|
||||
}
|
||||
|
||||
console.log(`Done`)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,650 @@
|
||||
const vm = require('vm')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const jsYaml = require('js-yaml')
|
||||
const isatty = require('tty').isatty(process.stdout.fd)
|
||||
const { installForTests } = require('./esbuild')
|
||||
|
||||
const testDir = path.join(__dirname, '..', 'demo', 'test262', 'test')
|
||||
const harnessDir = path.join(__dirname, '..', 'demo', 'test262', 'harness')
|
||||
|
||||
const progressBarLength = 64
|
||||
const eraseProgressBar = () => {
|
||||
previousProgressBar = null
|
||||
let text = `\r${' '.repeat(progressBarLength)}\r`
|
||||
if (printNewlineWhenErasing) {
|
||||
printNewlineWhenErasing = false
|
||||
text += '\n'
|
||||
}
|
||||
return text
|
||||
}
|
||||
let previousProgressBar = null
|
||||
let printNewlineWhenErasing = false
|
||||
|
||||
const resetColor = isatty ? `\x1b[0m` : ''
|
||||
const boldColor = isatty ? `\x1b[1m` : ''
|
||||
const dimColor = isatty ? `\x1b[37m` : ''
|
||||
const underlineColor = isatty ? `\x1b[4m` : ''
|
||||
|
||||
const redColor = isatty ? `\x1b[31m` : ''
|
||||
const greenColor = isatty ? `\x1b[32m` : ''
|
||||
const blueColor = isatty ? `\x1b[34m` : ''
|
||||
|
||||
const cyanColor = isatty ? `\x1b[36m` : ''
|
||||
const magentaColor = isatty ? `\x1b[35m` : ''
|
||||
const yellowColor = isatty ? `\x1b[33m` : ''
|
||||
|
||||
const redBgRedColor = isatty ? `\x1b[41;31m` : ''
|
||||
const redBgWhiteColor = isatty ? `\x1b[41;97m` : ''
|
||||
const greenBgGreenColor = isatty ? `\x1b[42;32m` : ''
|
||||
const greenBgWhiteColor = isatty ? `\x1b[42;97m` : ''
|
||||
const blueBgBlueColor = isatty ? `\x1b[44;34m` : ''
|
||||
const blueBgWhiteColor = isatty ? `\x1b[44;97m` : ''
|
||||
|
||||
const cyanBgCyanColor = isatty ? `\x1b[46;36m` : ''
|
||||
const cyanBgBlackColor = isatty ? `\x1b[46;30m` : ''
|
||||
const magentaBgMagentaColor = isatty ? `\x1b[45;35m` : ''
|
||||
const magentaBgBlackColor = isatty ? `\x1b[45;30m` : ''
|
||||
const yellowBgYellowColor = isatty ? `\x1b[43;33m` : ''
|
||||
const yellowBgBlackColor = isatty ? `\x1b[43;30m` : ''
|
||||
|
||||
const whiteBgWhiteColor = isatty ? `\x1b[107;97m` : ''
|
||||
const whiteBgBlackColor = isatty ? `\x1b[107;30m` : ''
|
||||
|
||||
const skipTheseFeatures = new Set([
|
||||
'regexp-v-flag',
|
||||
'regexp-match-indices',
|
||||
'regexp-named-groups',
|
||||
'regexp-unicode-property-escapes',
|
||||
])
|
||||
|
||||
const skipTheseTests = new Set([
|
||||
// Skip these tests because esbuild deliberately always supports ESM syntax
|
||||
// in all files. Also esbuild doesn't support the script goal at all.
|
||||
'language/expressions/import.meta/syntax/goal-script.js',
|
||||
'language/global-code/export.js',
|
||||
'language/global-code/import.js',
|
||||
|
||||
// Skip these tests because we deliberately support top-level return (input
|
||||
// files are treated as CommonJS and/or ESM but never as global code, and
|
||||
// top-level return is allowed in CommonJS)
|
||||
'language/statements/return/S12.9_A1_T1.js',
|
||||
'language/statements/return/S12.9_A1_T10.js',
|
||||
'language/statements/return/S12.9_A1_T2.js',
|
||||
'language/statements/return/S12.9_A1_T3.js',
|
||||
'language/statements/return/S12.9_A1_T4.js',
|
||||
'language/statements/return/S12.9_A1_T5.js',
|
||||
'language/statements/return/S12.9_A1_T6.js',
|
||||
'language/statements/return/S12.9_A1_T7.js',
|
||||
'language/statements/return/S12.9_A1_T8.js',
|
||||
'language/statements/return/S12.9_A1_T9.js',
|
||||
'language/global-code/return.js',
|
||||
|
||||
// Skip these tests because we deliberately support parsing top-level await
|
||||
// in all files. Files containing top-level await are always interpreted as
|
||||
// ESM, never as CommonJS.
|
||||
'language/expressions/assignmenttargettype/simple-basic-identifierreference-await.js',
|
||||
'language/expressions/await/await-BindingIdentifier-in-global.js',
|
||||
'language/expressions/await/await-in-global.js',
|
||||
'language/expressions/await/await-in-nested-function.js',
|
||||
'language/expressions/await/await-in-nested-generator.js',
|
||||
'language/expressions/class/class-name-ident-await-escaped.js',
|
||||
'language/expressions/class/class-name-ident-await.js',
|
||||
'language/expressions/class/static-init-await-reference.js',
|
||||
'language/expressions/dynamic-import/2nd-param-await-ident.js',
|
||||
'language/expressions/dynamic-import/assignment-expression/await-identifier.js',
|
||||
'language/expressions/function/static-init-await-reference.js',
|
||||
'language/expressions/generators/static-init-await-reference.js',
|
||||
'language/expressions/in/private-field-rhs-await-absent.js',
|
||||
'language/expressions/object/identifier-shorthand-await-strict-mode.js',
|
||||
'language/expressions/object/method-definition/static-init-await-reference-accessor.js',
|
||||
'language/expressions/object/method-definition/static-init-await-reference-generator.js',
|
||||
'language/expressions/object/method-definition/static-init-await-reference-normal.js',
|
||||
'language/module-code/top-level-await/new-await-script-code.js',
|
||||
'language/reserved-words/await-script.js',
|
||||
'language/statements/class/class-name-ident-await-escaped.js',
|
||||
'language/statements/class/class-name-ident-await.js',
|
||||
'language/statements/labeled/value-await-non-module-escaped.js',
|
||||
'language/statements/labeled/value-await-non-module.js',
|
||||
|
||||
// Skip these tests because we don't currently validate the contents of
|
||||
// regular expressions. We could do this but it's not necessary to parse
|
||||
// JavaScript successfully since we parse enough of it to be able to
|
||||
// determine where the regular expression ends (just "\" and "[]" pairs).
|
||||
'language/literals/regexp/early-err-pattern.js',
|
||||
'language/literals/regexp/invalid-braced-quantifier-exact.js',
|
||||
'language/literals/regexp/invalid-braced-quantifier-lower.js',
|
||||
'language/literals/regexp/invalid-braced-quantifier-range.js',
|
||||
'language/literals/regexp/invalid-optional-lookbehind.js',
|
||||
'language/literals/regexp/invalid-optional-negative-lookbehind.js',
|
||||
'language/literals/regexp/invalid-range-lookbehind.js',
|
||||
'language/literals/regexp/invalid-range-negative-lookbehind.js',
|
||||
'language/literals/regexp/u-invalid-class-escape.js',
|
||||
'language/literals/regexp/u-invalid-extended-pattern-char.js',
|
||||
'language/literals/regexp/u-invalid-identity-escape.js',
|
||||
'language/literals/regexp/u-invalid-legacy-octal-escape.js',
|
||||
'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-a.js',
|
||||
'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-ab.js',
|
||||
'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-b.js',
|
||||
'language/literals/regexp/u-invalid-non-empty-class-ranges.js',
|
||||
'language/literals/regexp/u-invalid-oob-decimal-escape.js',
|
||||
'language/literals/regexp/u-invalid-optional-lookahead.js',
|
||||
'language/literals/regexp/u-invalid-optional-lookbehind.js',
|
||||
'language/literals/regexp/u-invalid-optional-negative-lookahead.js',
|
||||
'language/literals/regexp/u-invalid-optional-negative-lookbehind.js',
|
||||
'language/literals/regexp/u-invalid-range-lookahead.js',
|
||||
'language/literals/regexp/u-invalid-range-lookbehind.js',
|
||||
'language/literals/regexp/u-invalid-range-negative-lookahead.js',
|
||||
'language/literals/regexp/u-invalid-range-negative-lookbehind.js',
|
||||
'language/literals/regexp/u-unicode-esc-bounds.js',
|
||||
'language/literals/regexp/u-unicode-esc-non-hex.js',
|
||||
'language/literals/regexp/unicode-escape-nls-err.js',
|
||||
])
|
||||
|
||||
const skipEvaluatingTheseIncludes = new Set([
|
||||
'nativeFunctionMatcher.js', // We don't preserve "toString()" on functions
|
||||
])
|
||||
|
||||
const skipEvaluatingTheseFeatures = new Set([
|
||||
// Node's version of V8 doesn't implement these
|
||||
'hashbang',
|
||||
'legacy-regexp',
|
||||
'regexp-duplicate-named-groups',
|
||||
'symbols-as-weakmap-keys',
|
||||
'tail-call-optimization',
|
||||
|
||||
// We don't care about API-related things
|
||||
'ArrayBuffer',
|
||||
'change-array-by-copy',
|
||||
'DataView',
|
||||
'resizable-arraybuffer',
|
||||
'ShadowRealm',
|
||||
'SharedArrayBuffer',
|
||||
'String.prototype.toWellFormed',
|
||||
'Symbol.match',
|
||||
'Symbol.replace',
|
||||
'Symbol.unscopables',
|
||||
'Temporal',
|
||||
'TypedArray',
|
||||
])
|
||||
|
||||
const skipEvaluatingTheseTests = new Set([
|
||||
// This test is skipped because it crashes V8:
|
||||
//
|
||||
// #
|
||||
// # Fatal error in , line 0
|
||||
// # Check failed: i < self->length().
|
||||
// #
|
||||
// #
|
||||
// #
|
||||
// #FailureMessage Object: 0x7fffac9a1c30
|
||||
// 1: 0xc2abf1 [node]
|
||||
// 2: 0x1f231d4 V8_Fatal(char const*, ...) [node]
|
||||
// 3: 0xdac209 v8::FixedArray::Get(v8::Local<v8::Context>, int) const [node]
|
||||
// 4: 0xb74ab7 [node]
|
||||
// 5: 0xb76a2d [node]
|
||||
// 6: 0xf2436f v8::internal::Isolate::RunHostImportModuleDynamicallyCallback(v8::internal::MaybeHandle<v8::internal::Script>,
|
||||
// v8::internal::Handle<v8::internal::Object>, v8::internal::MaybeHandle<v8::internal::Object>) [node]
|
||||
// 7: 0x137e3db v8::internal::Runtime_DynamicImportCall(int, unsigned long*, v8::internal::Isolate*) [node]
|
||||
// 8: 0x17fd4f4 [node]
|
||||
//
|
||||
'language/expressions/dynamic-import/import-assertions/2nd-param-assert-enumeration.js',
|
||||
])
|
||||
|
||||
function findFiles() {
|
||||
function visit(dir) {
|
||||
for (const entry of fs.readdirSync(dir)) {
|
||||
const fullEntry = path.join(dir, entry)
|
||||
const stats = fs.statSync(fullEntry)
|
||||
if (stats.isDirectory()) {
|
||||
visit(fullEntry)
|
||||
} else if (stats.isFile() && entry.endsWith('.js') && !entry.includes('_FIXTURE')) {
|
||||
files.push(fullEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const files = []
|
||||
for (const entry of fs.readdirSync(testDir)) {
|
||||
if (entry === 'staging' || entry === 'intl402' || entry === 'built-ins') continue // We're not interested in these
|
||||
visit(path.join(testDir, entry))
|
||||
}
|
||||
|
||||
// Reverse for faster iteration times because many of the more interesting tests come last
|
||||
return files.reverse()
|
||||
}
|
||||
|
||||
async function checkTransformAPI({ esbuild, file, content, yaml }) {
|
||||
if (yaml.flags) {
|
||||
if (yaml.flags.includes('onlyStrict')) content = '"use strict";' + content
|
||||
if (yaml.flags.includes('module')) content += '\nexport {}'
|
||||
}
|
||||
|
||||
// Step 1: Try transforming the file normally
|
||||
const shouldParse = !yaml.negative || yaml.negative.phase !== 'parse'
|
||||
let result
|
||||
try {
|
||||
result = await esbuild.transform(content, { sourcefile: file })
|
||||
} catch (error) {
|
||||
if (shouldParse) {
|
||||
error.kind = 'Transform'
|
||||
throw error
|
||||
}
|
||||
return // Stop now if this test is supposed to fail
|
||||
}
|
||||
if (!shouldParse) {
|
||||
const error = new Error('Unexpected successful transform')
|
||||
error.kind = 'Transform'
|
||||
throw error
|
||||
}
|
||||
|
||||
// Step 2: Try transforming the output again (this should always succeed)
|
||||
let result2
|
||||
try {
|
||||
result2 = await esbuild.transform(result.code, { sourcefile: file })
|
||||
} catch (error) {
|
||||
error.kind = 'Reparse'
|
||||
throw error
|
||||
}
|
||||
|
||||
// Step 3: The output should be the same the second time
|
||||
if (result2.code !== result.code) {
|
||||
const lines = result.code.split('\n')
|
||||
const lines2 = result2.code.split('\n')
|
||||
let i = 0
|
||||
while (i < lines.length && i < lines2.length && lines[i] === lines2[i]) i++
|
||||
const error = { toString: () => `${redColor}-${lines[i]}\n${greenColor}+${lines2[i]}${resetColor}` }
|
||||
error.kind = 'Reprint'
|
||||
throw error
|
||||
}
|
||||
|
||||
// Step 4: Try minifying the output once
|
||||
let result4
|
||||
try {
|
||||
result4 = await esbuild.transform(result2.code, { sourcefile: file, minify: true })
|
||||
} catch (error) {
|
||||
error.kind = 'Minify'
|
||||
throw error
|
||||
}
|
||||
|
||||
// Step 5: Try minifying the output again
|
||||
let result5
|
||||
try {
|
||||
result5 = await esbuild.transform(result4.code, { sourcefile: file, minify: true })
|
||||
} catch (error) {
|
||||
error.kind = 'Minify'
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function checkBuildAPI({ esbuild, file, content, yaml }) {
|
||||
const plugins = []
|
||||
if (yaml.flags) {
|
||||
const isOnlyStrict = yaml.flags.includes('onlyStrict')
|
||||
const isModule = yaml.flags.includes('module')
|
||||
if (isOnlyStrict || isModule) {
|
||||
plugins.push({
|
||||
name: 'modify',
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /./ }, args => {
|
||||
if (args.path === file) {
|
||||
let loaded = content
|
||||
if (isOnlyStrict) loaded = '"use strict";' + loaded
|
||||
if (isModule) loaded += '\nexport {}'
|
||||
return { contents: loaded }
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Try building the file normally
|
||||
const isModule = yaml.flags && yaml.flags.includes('module')
|
||||
const isDynamicImport = yaml.flags && yaml.flags.includes('dynamic-import')
|
||||
const isAsync = yaml.flags && yaml.flags.includes('async')
|
||||
const shouldParse = !yaml.negative || yaml.negative.phase === 'runtime'
|
||||
let result
|
||||
try {
|
||||
const options = {
|
||||
entryPoints: [file],
|
||||
write: false,
|
||||
keepNames: true,
|
||||
logLevel: 'silent',
|
||||
plugins,
|
||||
target: 'node' + process.version.slice(1),
|
||||
logOverride: { 'direct-eval': 'warning' },
|
||||
}
|
||||
if (isModule || isDynamicImport || isAsync) {
|
||||
options.bundle = true
|
||||
options.format = isModule ? 'esm' : 'iife'
|
||||
options.external = [
|
||||
'', // Some tests use this as a dummy argument to an "import('')" that is never evaluated
|
||||
]
|
||||
}
|
||||
result = await esbuild.build(options)
|
||||
} catch (error) {
|
||||
if (shouldParse) {
|
||||
error.kind = 'Build'
|
||||
throw error
|
||||
}
|
||||
return // Stop now if this test is supposed to fail
|
||||
}
|
||||
if (!shouldParse) {
|
||||
const error = new Error('Unexpected successful build')
|
||||
error.kind = 'Build'
|
||||
throw error
|
||||
}
|
||||
|
||||
// Don't evaluate problematic files
|
||||
const hasDirectEval = result.warnings.some(msg => msg.id === 'direct-eval')
|
||||
if (
|
||||
hasDirectEval ||
|
||||
skipEvaluatingTheseTests.has(path.relative(testDir, file)) ||
|
||||
(yaml.includes && yaml.includes.some(include => skipEvaluatingTheseIncludes.has(include))) ||
|
||||
(yaml.features && yaml.features.some(feature => skipEvaluatingTheseFeatures.has(feature)))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Step 3: Try evaluating the file using node
|
||||
const importDir = path.dirname(file)
|
||||
const shouldEvaluate = !yaml.negative
|
||||
try {
|
||||
await runCodeInHarness(yaml, content, importDir)
|
||||
} catch (error) {
|
||||
// Ignore tests that fail when run in node
|
||||
if (shouldEvaluate) console.log(eraseProgressBar() + dimColor + `IGNORING ${path.relative(testDir, file)}: ${error}` + resetColor)
|
||||
return
|
||||
}
|
||||
if (!shouldEvaluate) {
|
||||
// Ignore tests that incorrectly pass when run in node
|
||||
return
|
||||
}
|
||||
|
||||
// Step 3: Try evaluating the file we generated
|
||||
const code = result.outputFiles[0].text
|
||||
try {
|
||||
await runCodeInHarness(yaml, code, importDir)
|
||||
} catch (error) {
|
||||
if (shouldEvaluate) {
|
||||
if (typeof error === 'string') error = new Error(error)
|
||||
error.kind = 'Evaluate'
|
||||
throw error
|
||||
}
|
||||
return // Stop now if this test is supposed to fail
|
||||
}
|
||||
if (!shouldEvaluate) {
|
||||
const error = new Error('Unexpected successful evaluation')
|
||||
error.kind = 'Evaluate'
|
||||
throw error
|
||||
}
|
||||
|
||||
// Step 4: If evaluation worked and was supposed to work, check to see
|
||||
// if it still works after running esbuild's syntax lowering pass on it
|
||||
for (let version = 2015; version <= 2022; version++) {
|
||||
let result
|
||||
try {
|
||||
result = await esbuild.transform(code, { sourcefile: file, target: `es${version}` })
|
||||
} catch (error) {
|
||||
continue // This means esbuild doesn't support lowering this code to this old a version
|
||||
}
|
||||
try {
|
||||
await runCodeInHarness(yaml, code, importDir)
|
||||
} catch (error) {
|
||||
if (typeof error === 'string') error = new Error(error)
|
||||
error.kind = 'Lower'
|
||||
throw error
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const startTime = Date.now()
|
||||
|
||||
// Get this warning out of the way so it's not in the middle of our output.
|
||||
// Note: If this constructor is missing, you need to run node with the
|
||||
// "--experimental-vm-modules" flag (and use a version of node that has it).
|
||||
{
|
||||
const temp = new vm.SourceTextModule('')
|
||||
await temp.link(() => { throw new Error })
|
||||
await temp.evaluate()
|
||||
}
|
||||
|
||||
console.log(`\n${dimColor}Finding tests...${resetColor}`)
|
||||
const files = findFiles()
|
||||
console.log(`Found ${files.length} test files`)
|
||||
|
||||
console.log(`\n${dimColor}Installing esbuild...${resetColor}`)
|
||||
const esbuild = installForTests()
|
||||
|
||||
console.log(`\n${dimColor}Running tests...${resetColor}\n`)
|
||||
const errorCounts = {}
|
||||
let skippedCount = 0
|
||||
|
||||
await forEachInParallel(files, 32, async (file) => {
|
||||
// Don't parse files that esbuild deliberately handles differently
|
||||
if (skipTheseTests.has(path.relative(testDir, file))) {
|
||||
skippedCount++
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(file, 'utf8')
|
||||
const start = content.indexOf('/*---')
|
||||
const end = content.indexOf('---*/')
|
||||
if (start < 0 || end < 0) throw new Error(`Missing YAML metadata`)
|
||||
const yaml = jsYaml.safeLoad(content.slice(start + 5, end))
|
||||
|
||||
// Don't parse files that test things we don't care about
|
||||
if (yaml.features && yaml.features.some(feature => skipTheseFeatures.has(feature))) {
|
||||
skippedCount++
|
||||
return
|
||||
}
|
||||
|
||||
await checkTransformAPI({ esbuild, file, content, yaml })
|
||||
await checkBuildAPI({ esbuild, file, content, yaml })
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
errorCounts[error.kind] = (errorCounts[error.kind] || 0) + 1
|
||||
printError(file, error)
|
||||
}
|
||||
})
|
||||
|
||||
const table = []
|
||||
table.push(['Total tests', `${files.length}`])
|
||||
table.push(['Tests ran', `${files.length - skippedCount}`])
|
||||
table.push(['Tests skipped', `${skippedCount}`])
|
||||
for (const kind of Object.keys(errorCounts).sort()) {
|
||||
table.push([kind + ' errors', `${errorCounts[kind]}`])
|
||||
}
|
||||
const seconds = (Date.now() - startTime) / 1000
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
table.push(['Time taken', `${minutes ? `${minutes} min ${+(seconds - minutes * 60).toFixed(1)} sec` : `${+seconds.toFixed(1)} sec`}`])
|
||||
const maxLength = Math.max(...table.map(x => x[0].length))
|
||||
printNewlineWhenErasing = true
|
||||
process.stdout.write(eraseProgressBar())
|
||||
for (const [key, value] of table) {
|
||||
console.log(`${boldColor}${(key + ':').padEnd(maxLength + 1)}${resetColor} ${value}`)
|
||||
}
|
||||
}
|
||||
|
||||
function forEachInParallel(items, batchSize, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let inFlight = 0
|
||||
let i = 0
|
||||
|
||||
function next() {
|
||||
if (i === items.length && inFlight === 0) {
|
||||
process.stdout.write(eraseProgressBar())
|
||||
return resolve()
|
||||
}
|
||||
|
||||
const completed = Math.floor(progressBarLength * i / items.length)
|
||||
if (previousProgressBar !== completed) {
|
||||
previousProgressBar = completed
|
||||
const progressHead = '\u2501'.repeat(Math.max(0, completed - 1))
|
||||
const progressBoundary = completed ? '\u252B' : ''
|
||||
const progressTail = '\u2500'.repeat(progressBarLength - completed)
|
||||
process.stdout.write(`\r` + greenColor + progressHead + progressBoundary + dimColor + progressTail + resetColor)
|
||||
}
|
||||
|
||||
while (i < items.length && inFlight < batchSize) {
|
||||
inFlight++
|
||||
callback(items[i++]).then(() => {
|
||||
inFlight--
|
||||
next()
|
||||
}, reject)
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
const harnessFiles = new Map
|
||||
let defaultHarness = ''
|
||||
|
||||
for (const entry of fs.readdirSync(harnessDir)) {
|
||||
if (entry.startsWith('.') || !entry.endsWith('.js')) {
|
||||
continue
|
||||
}
|
||||
const file = path.join(harnessDir, entry)
|
||||
const content = fs.readFileSync(file, 'utf8')
|
||||
if (entry === 'assert.js' || entry === 'sta.js') {
|
||||
defaultHarness += content
|
||||
continue
|
||||
}
|
||||
harnessFiles.set(entry, content)
|
||||
}
|
||||
|
||||
function createHarnessForTest(yaml) {
|
||||
let harness = defaultHarness
|
||||
|
||||
if (yaml.includes) {
|
||||
for (const include of yaml.includes) {
|
||||
const content = harnessFiles.get(include)
|
||||
if (!content) throw new Error(`Included file is missing: ${include}`)
|
||||
harness += content
|
||||
}
|
||||
}
|
||||
|
||||
return harness
|
||||
}
|
||||
|
||||
async function runCodeInHarness(yaml, code, importDir) {
|
||||
const context = {}
|
||||
const isAsync = yaml.flags && yaml.flags.includes('async')
|
||||
const isModule = yaml.flags && yaml.flags.includes('module')
|
||||
const isRaw = yaml.flags && yaml.flags.includes('raw')
|
||||
|
||||
// See: https://github.com/nodejs/node/issues/36351
|
||||
const unique = () => '//' + Math.random()
|
||||
|
||||
const runCode = async () => {
|
||||
const moduleCache = new Map
|
||||
const dynamicImportCache = new Map
|
||||
|
||||
const findModule = (modulePath) => {
|
||||
let module = moduleCache.get(modulePath)
|
||||
if (!module) {
|
||||
const code = fs.readFileSync(modulePath, 'utf8')
|
||||
if (modulePath.endsWith('json')) {
|
||||
const evaluate = function () {
|
||||
this.setExport('default', vm.runInContext('JSON.parse', context)(code))
|
||||
}
|
||||
module = new vm.SyntheticModule(['default'], evaluate, { context })
|
||||
} else {
|
||||
module = new vm.SourceTextModule(code + unique(), { context, importModuleDynamically })
|
||||
}
|
||||
moduleCache.set(modulePath, module)
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
const linker = (specifier, referencingModule) => {
|
||||
return findModule(path.join(importDir, specifier))
|
||||
}
|
||||
|
||||
const importModuleDynamically = (specifier, script) => {
|
||||
const where = path.join(importDir, specifier)
|
||||
let promise = dynamicImportCache.get(where)
|
||||
if (!promise) {
|
||||
const module = findModule(where, context)
|
||||
if (module.status === 'unlinked') {
|
||||
promise = module.link(linker)
|
||||
.then(() => module.evaluate())
|
||||
.then(() => module)
|
||||
} else {
|
||||
promise = Promise.resolve(module)
|
||||
}
|
||||
dynamicImportCache.set(where, promise)
|
||||
}
|
||||
return promise
|
||||
}
|
||||
|
||||
vm.createContext(context)
|
||||
if (!isRaw) vm.runInContext(createHarnessForTest(yaml), context)
|
||||
|
||||
if (isModule) {
|
||||
const module = new vm.SourceTextModule(code + unique(), { context, importModuleDynamically })
|
||||
await module.link(linker)
|
||||
await module.evaluate()
|
||||
} else {
|
||||
const script = new vm.Script(code, { importModuleDynamically })
|
||||
script.runInContext(context)
|
||||
}
|
||||
}
|
||||
|
||||
if (isAsync) {
|
||||
await new Promise((resolve, reject) => {
|
||||
context.$DONE = err => err ? reject(err) : resolve()
|
||||
runCode(code, context).catch(reject)
|
||||
})
|
||||
} else {
|
||||
await runCode(code, context)
|
||||
}
|
||||
}
|
||||
|
||||
function printError(file, error) {
|
||||
let detail
|
||||
|
||||
if (error.errors) {
|
||||
const { text, location } = error.errors[0]
|
||||
if (location) {
|
||||
const { file, line, column, lineText, length } = location
|
||||
detail = ' ' + dimColor + path.basename(file) + ':' + line + ':' + column + ': ' + resetColor + text + '\n' +
|
||||
' ' + dimColor + lineText.slice(0, column) + greenColor + lineText.slice(column, column + length) + dimColor + lineText.slice(column + length) + resetColor + '\n' +
|
||||
' ' + greenColor + ' '.repeat(column) + (length > 1 ? '~'.repeat(length) : '^') + resetColor
|
||||
} else {
|
||||
detail = dimColor + ('\n' + text).split('\n').join('\n ').slice(1) + resetColor
|
||||
}
|
||||
} else {
|
||||
detail = dimColor + ('\n' + error).split('\n').join('\n ').slice(1) + resetColor
|
||||
}
|
||||
|
||||
const prettyPath = path.relative(testDir, file)
|
||||
printNewlineWhenErasing = true
|
||||
console.log(eraseProgressBar() + tagMap[error.kind] + ' ' + prettyPath + '\n' + detail)
|
||||
printNewlineWhenErasing = true
|
||||
}
|
||||
|
||||
const tagMap = {
|
||||
Transform: redBgRedColor + `[` + redBgWhiteColor + `TRANSFORM ERROR` + redBgRedColor + `]` + resetColor,
|
||||
Build: magentaBgMagentaColor + `[` + magentaBgBlackColor + `BUILD ERROR` + magentaBgMagentaColor + `]` + resetColor,
|
||||
Reparse: yellowBgYellowColor + `[` + yellowBgBlackColor + `REPARSE ERROR` + yellowBgYellowColor + `]` + resetColor,
|
||||
Reprint: cyanBgCyanColor + `[` + cyanBgBlackColor + `REPRINT ERROR` + cyanBgCyanColor + `]` + resetColor,
|
||||
Minify: blueBgBlueColor + `[` + blueBgWhiteColor + `MINIFY ERROR` + blueBgBlueColor + `]` + resetColor,
|
||||
Evaluate: greenBgGreenColor + `[` + greenBgWhiteColor + `EVALUATE ERROR` + greenBgGreenColor + `]` + resetColor,
|
||||
Lower: whiteBgWhiteColor + `[` + whiteBgBlackColor + `LOWER ERROR` + whiteBgWhiteColor + `]` + resetColor,
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', () => {
|
||||
// Don't exit when a test does this
|
||||
})
|
||||
|
||||
main().catch(e => setTimeout(() => {
|
||||
throw e
|
||||
}))
|
||||
@@ -0,0 +1,432 @@
|
||||
<!-- This is a simple playground to try out esbuild in your browser -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Try esbuild live</title>
|
||||
<style>
|
||||
body {
|
||||
font: 15px/120% sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2,
|
||||
p {
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
textarea,
|
||||
#output .outer {
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
color: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 110%;
|
||||
font-family: monospace;
|
||||
cursor: text;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
#output .outer {
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.minified {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #999;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
section p {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
#input {
|
||||
left: 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#output {
|
||||
right: 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.outer {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 120px;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
#input .outer {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
#output .outer {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #EEE;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
textarea,
|
||||
#output .outer {
|
||||
background: #FFF;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.terminal-1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.terminal-37 {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.terminal-4 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.terminal-31 {
|
||||
color: #D00;
|
||||
}
|
||||
|
||||
.terminal-32 {
|
||||
color: #0D0;
|
||||
}
|
||||
|
||||
.terminal-33 {
|
||||
color: #CC0;
|
||||
}
|
||||
|
||||
.terminal-35 {
|
||||
color: #D0D;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
color: #DDD;
|
||||
}
|
||||
|
||||
textarea,
|
||||
#output .outer {
|
||||
background: #111;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.terminal-37 {
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="input">
|
||||
<h2>Input</h2>
|
||||
<p>
|
||||
<label for="target">
|
||||
Target: <select id="target">
|
||||
<option>ES5</option>
|
||||
<option>ES2015</option>
|
||||
<option>ES2016</option>
|
||||
<option>ES2017</option>
|
||||
<option>ES2018</option>
|
||||
<option>ES2019</option>
|
||||
<option>ES2020</option>
|
||||
<option>ES2021</option>
|
||||
<option selected>ESNext</option>
|
||||
|
||||
<option>Chrome70</option>
|
||||
<option>Chrome71</option>
|
||||
<option>Chrome72</option>
|
||||
<option>Chrome73</option>
|
||||
<option>Chrome74</option>
|
||||
<option>Chrome75</option>
|
||||
<option>Chrome76</option>
|
||||
<option>Chrome77</option>
|
||||
<option>Chrome78</option>
|
||||
<option>Chrome79</option>
|
||||
|
||||
<option>Chrome80</option>
|
||||
<option>Chrome81</option>
|
||||
<option>Chrome82</option>
|
||||
<option>Chrome83</option>
|
||||
<option>Chrome84</option>
|
||||
<option>Chrome85</option>
|
||||
<option>Chrome86</option>
|
||||
<option>Chrome87</option>
|
||||
<option>Chrome88</option>
|
||||
<option>Chrome89</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label for="loader">
|
||||
Loader: <select id="loader">
|
||||
<option selected>JS</option>
|
||||
<option>JSX</option>
|
||||
<option>TS</option>
|
||||
<option>TSX</option>
|
||||
<option>CSS</option>
|
||||
<option>JSON</option>
|
||||
<option>Text</option>
|
||||
<option>Base64</option>
|
||||
<option>DataURL</option>
|
||||
<option>Binary</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label for="format">
|
||||
Format: <select id="format">
|
||||
<option selected>Preserve</option>
|
||||
<option>IIFE</option>
|
||||
<option>CJS</option>
|
||||
<option>ESM</option>
|
||||
</select>
|
||||
</label>
|
||||
<br>
|
||||
<label for="bundle">
|
||||
<input id="bundle" type="checkbox">
|
||||
Bundle
|
||||
</label>
|
||||
|
||||
<label for="ascii">
|
||||
<input id="ascii" type="checkbox">
|
||||
ASCII
|
||||
</label>
|
||||
|
||||
<label for="keepNames">
|
||||
<input id="keepNames" type="checkbox">
|
||||
Keep Names
|
||||
</label>
|
||||
|
||||
<label for="mangleProps">
|
||||
<input id="mangleProps" type="checkbox">
|
||||
Mangle Props
|
||||
</label>
|
||||
|
||||
Minify:
|
||||
<label for="minify-syntax"><input id="minify-syntax" type="checkbox"> Syntax</label>
|
||||
<label for="minify-idents"><input id="minify-idents" type="checkbox"> Identifiers</label>
|
||||
<label for="minify-spaces"><input id="minify-spaces" type="checkbox"> Whitespace</label>
|
||||
</p>
|
||||
<div class="outer"><textarea autofocus spellcheck="false"></textarea></div>
|
||||
</section>
|
||||
<section id="output">
|
||||
<h2>Output</h2>
|
||||
<div class="outer"></div>
|
||||
</section>
|
||||
<script src="../npm/esbuild-wasm/lib/browser.js"></script>
|
||||
<script>
|
||||
const input = document.querySelector('#input textarea')
|
||||
const target = document.querySelector('#target')
|
||||
const loader = document.querySelector('#loader')
|
||||
const format = document.querySelector('#format')
|
||||
const minifySyntax = document.querySelector('#minify-syntax')
|
||||
const minifyIdents = document.querySelector('#minify-idents')
|
||||
const minifySpaces = document.querySelector('#minify-spaces')
|
||||
const ascii = document.querySelector('#ascii')
|
||||
const bundle = document.querySelector('#bundle')
|
||||
const keepNames = document.querySelector('#keepNames')
|
||||
const mangleProps = document.querySelector('#mangleProps')
|
||||
let runIfIdle
|
||||
|
||||
function persistValue(el, key) {
|
||||
el.onchange = () => {
|
||||
runIfIdle()
|
||||
try {
|
||||
sessionStorage.setItem(key, el.value)
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
const old = sessionStorage.getItem(key)
|
||||
if (old !== null) el.value = old
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
function persistChecked(el, key) {
|
||||
el.onchange = () => {
|
||||
runIfIdle()
|
||||
try {
|
||||
sessionStorage.setItem(key, el.checked)
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
const old = sessionStorage.getItem(key)
|
||||
if (old !== null) el.checked = old === 'true'
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
persistValue(target, 'target')
|
||||
persistValue(loader, 'loader')
|
||||
persistValue(format, 'format')
|
||||
persistChecked(minifySyntax, 'minifySyntax')
|
||||
persistChecked(minifyIdents, 'minifyIdents')
|
||||
persistChecked(minifySpaces, 'minifySpaces')
|
||||
persistChecked(ascii, 'ascii')
|
||||
persistChecked(bundle, 'bundle')
|
||||
persistChecked(keepNames, 'keepNames')
|
||||
persistChecked(mangleProps, 'mangleProps')
|
||||
|
||||
try {
|
||||
const old = sessionStorage.getItem('input')
|
||||
if (old !== null) input.value = old
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
esbuild.initialize({
|
||||
wasmURL: '../npm/esbuild-wasm/esbuild.wasm',
|
||||
}).then(() => {
|
||||
const output = document.querySelector('#output .outer')
|
||||
let debounceTimeout = 0
|
||||
let isRunning = false
|
||||
let needsRun = false
|
||||
|
||||
input.oninput = () => {
|
||||
clearTimeout(debounceTimeout)
|
||||
debounceTimeout = setTimeout(runIfIdle, 50)
|
||||
}
|
||||
|
||||
runIfIdle = async () => {
|
||||
clearTimeout(debounceTimeout)
|
||||
|
||||
if (isRunning) {
|
||||
needsRun = true
|
||||
return
|
||||
}
|
||||
|
||||
isRunning = true
|
||||
needsRun = false
|
||||
|
||||
const inputValue = input.value
|
||||
var code, warnings, errors
|
||||
try {
|
||||
if (bundle.checked) {
|
||||
const results = await esbuild.build({
|
||||
stdin: {
|
||||
contents: inputValue,
|
||||
loader: loader.value.toLowerCase(),
|
||||
},
|
||||
bundle: true,
|
||||
target: target.value.toLowerCase(),
|
||||
format: format.value === 'Preserve' ? void 0 : format.value.toLowerCase(),
|
||||
minifySyntax: minifySyntax.checked,
|
||||
minifyIdentifiers: minifyIdents.checked,
|
||||
minifyWhitespace: minifySpaces.checked,
|
||||
charset: ascii.checked ? 'ascii' : 'utf8',
|
||||
keepNames: keepNames.checked,
|
||||
mangleProps: mangleProps.checked ? /_$/ : void 0,
|
||||
plugins: [{
|
||||
name: 'external-all',
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /.*/ }, args => {
|
||||
return { path: args.path, external: true }
|
||||
})
|
||||
},
|
||||
}],
|
||||
})
|
||||
code = results.outputFiles[0].text
|
||||
warnings = results.warnings
|
||||
} else {
|
||||
({ code, warnings } = await esbuild.transform(inputValue, {
|
||||
target: target.value.toLowerCase(),
|
||||
loader: loader.value.toLowerCase(),
|
||||
format: format.value === 'Preserve' ? void 0 : format.value.toLowerCase(),
|
||||
minifySyntax: minifySyntax.checked,
|
||||
minifyIdentifiers: minifyIdents.checked,
|
||||
minifyWhitespace: minifySpaces.checked,
|
||||
charset: ascii.checked ? 'ascii' : 'utf8',
|
||||
keepNames: keepNames.checked,
|
||||
mangleProps: mangleProps.checked ? /_$/ : void 0,
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
({ errors, warnings } = error)
|
||||
if (!errors) {
|
||||
output.textContent = (error && error.message) || (error + '')
|
||||
isRunning = false
|
||||
if (needsRun) runIfIdle()
|
||||
return
|
||||
}
|
||||
}
|
||||
if (warnings) warnings = await esbuild.formatMessages(warnings, { kind: 'warning', color: true })
|
||||
if (errors) errors = await esbuild.formatMessages(errors, { kind: 'error', color: true })
|
||||
const html = (errors || []).concat(warnings || []).map(messageToHTML).join('')
|
||||
if (code) code = textToHTML(code)
|
||||
if (code && minifySpaces.checked) code = `<span class="minified">${code}</span>`
|
||||
output.innerHTML = (html && html + '<hr>' || '') + (code || '')
|
||||
|
||||
try {
|
||||
sessionStorage.setItem('input', inputValue)
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
isRunning = false
|
||||
if (needsRun) runIfIdle()
|
||||
}
|
||||
|
||||
function textToHTML(text) {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
|
||||
function messageToHTML(text) {
|
||||
let reset = ''
|
||||
text = textToHTML(text)
|
||||
text = text.replace(/\033\[([^m]+)m/g, (_, value) => {
|
||||
if (value === '0') {
|
||||
value = reset
|
||||
reset = ''
|
||||
} else {
|
||||
reset += '</span>'
|
||||
value = `<span class="terminal-${value}">`
|
||||
}
|
||||
return value
|
||||
})
|
||||
text += reset
|
||||
return text
|
||||
}
|
||||
|
||||
runIfIdle()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,464 @@
|
||||
const { removeRecursiveSync } = require('./esbuild')
|
||||
const child_process = require('child_process')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const tsconfigJson = {
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
strict: true,
|
||||
},
|
||||
}
|
||||
|
||||
const testsWithoutErrors = {
|
||||
emptyBuildRequire: `
|
||||
export {}
|
||||
import esbuild = require('esbuild')
|
||||
esbuild.buildSync({})
|
||||
esbuild.build({})
|
||||
`,
|
||||
emptyBuildImport: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.buildSync({})
|
||||
esbuild.build({})
|
||||
`,
|
||||
emptyTransformRequire: `
|
||||
export {}
|
||||
import esbuild = require('esbuild')
|
||||
esbuild.transformSync('')
|
||||
esbuild.transformSync('', {})
|
||||
esbuild.transform('')
|
||||
esbuild.transform('', {})
|
||||
`,
|
||||
emptyTransformImport: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transformSync('')
|
||||
esbuild.transformSync('', {})
|
||||
esbuild.transform('')
|
||||
esbuild.transform('', {})
|
||||
`,
|
||||
|
||||
mangleCache: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.buildSync({ mangleCache: {} }).mangleCache['x']
|
||||
esbuild.build({ mangleCache: {} })
|
||||
.then(result => result.mangleCache['x'])
|
||||
esbuild.transformSync('', { mangleCache: {} }).mangleCache['x']
|
||||
esbuild.transform('', { mangleCache: {} })
|
||||
.then(result => result.mangleCache['x'])
|
||||
`,
|
||||
legalCommentsExternal: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transformSync('', { legalComments: 'external' }).legalComments.length
|
||||
esbuild.transform('', { legalComments: 'external' })
|
||||
.then(result => result.legalComments.length)
|
||||
`,
|
||||
writeFalseOutputFiles: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.buildSync({ write: false }).outputFiles[0]
|
||||
esbuild.build({ write: false })
|
||||
.then(result => result.outputFiles[0])
|
||||
`,
|
||||
metafileTrue: `
|
||||
import {build, buildSync, analyzeMetafile} from 'esbuild';
|
||||
analyzeMetafile(buildSync({ metafile: true }).metafile)
|
||||
build({ metafile: true })
|
||||
.then(result => analyzeMetafile(result.metafile))
|
||||
`,
|
||||
|
||||
contextMangleCache: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.context({ mangleCache: {} })
|
||||
.then(context => context.rebuild())
|
||||
.then(result => result.mangleCache['x'])
|
||||
`,
|
||||
contextWriteFalseOutputFiles: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.context({ write: false })
|
||||
.then(context => context.rebuild())
|
||||
.then(result => result.outputFiles[0])
|
||||
`,
|
||||
contextMetafileTrue: `
|
||||
import {context, analyzeMetafile} from 'esbuild';
|
||||
context({ metafile: true })
|
||||
.then(context => context.rebuild())
|
||||
.then(result => analyzeMetafile(result.metafile))
|
||||
`,
|
||||
|
||||
allOptionsTransform: `
|
||||
export {}
|
||||
import {transform} from 'esbuild'
|
||||
transform('', {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
globalName: '',
|
||||
target: 'esnext',
|
||||
minify: true,
|
||||
minifyWhitespace: true,
|
||||
minifyIdentifiers: true,
|
||||
minifySyntax: true,
|
||||
charset: 'utf8',
|
||||
jsxFactory: '',
|
||||
jsxFragment: '',
|
||||
define: { 'x': 'y' },
|
||||
pure: ['x'],
|
||||
color: true,
|
||||
logLevel: 'info',
|
||||
logLimit: 0,
|
||||
tsconfigRaw: {
|
||||
compilerOptions: {
|
||||
jsxFactory: '',
|
||||
jsxFragmentFactory: '',
|
||||
useDefineForClassFields: true,
|
||||
importsNotUsedAsValues: 'preserve',
|
||||
preserveValueImports: true,
|
||||
},
|
||||
},
|
||||
sourcefile: '',
|
||||
loader: 'ts',
|
||||
}).then(result => {
|
||||
let code: string = result.code;
|
||||
let map: string = result.map;
|
||||
for (let msg of result.warnings) {
|
||||
let text: string = msg.text
|
||||
if (msg.location !== null) {
|
||||
let file: string = msg.location.file;
|
||||
let namespace: string = msg.location.namespace;
|
||||
let line: number = msg.location.line;
|
||||
let column: number = msg.location.column;
|
||||
let length: number = msg.location.length;
|
||||
let lineText: string = msg.location.lineText;
|
||||
}
|
||||
}
|
||||
})
|
||||
`,
|
||||
allOptionsBuild: `
|
||||
export {}
|
||||
import {build} from 'esbuild'
|
||||
build({
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
globalName: '',
|
||||
target: 'esnext',
|
||||
minify: true,
|
||||
minifyWhitespace: true,
|
||||
minifyIdentifiers: true,
|
||||
minifySyntax: true,
|
||||
charset: 'utf8',
|
||||
jsxFactory: '',
|
||||
jsxFragment: '',
|
||||
define: { 'x': 'y' },
|
||||
pure: ['x'],
|
||||
color: true,
|
||||
logLevel: 'info',
|
||||
logLimit: 0,
|
||||
bundle: true,
|
||||
splitting: true,
|
||||
outfile: '',
|
||||
metafile: true,
|
||||
outdir: '',
|
||||
outbase: '',
|
||||
platform: 'node',
|
||||
external: ['x'],
|
||||
loader: { 'x': 'ts' },
|
||||
resolveExtensions: ['x'],
|
||||
mainFields: ['x'],
|
||||
write: true,
|
||||
tsconfig: 'x',
|
||||
outExtension: { 'x': 'y' },
|
||||
publicPath: 'x',
|
||||
inject: ['x'],
|
||||
entryPoints: ['x'],
|
||||
stdin: {
|
||||
contents: '',
|
||||
resolveDir: '',
|
||||
sourcefile: '',
|
||||
loader: 'ts',
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'x',
|
||||
setup(build) {
|
||||
build.onStart(() => {})
|
||||
build.onStart(async () => {})
|
||||
build.onStart(() => ({ warnings: [{text: '', location: {file: '', line: 0}}] }))
|
||||
build.onStart(async () => ({ warnings: [{text: '', location: {file: '', line: 0}}] }))
|
||||
build.onEnd(result => {})
|
||||
build.onEnd(async result => {})
|
||||
build.onLoad({filter: /./}, () => undefined)
|
||||
build.onResolve({filter: /./, namespace: ''}, args => {
|
||||
let path: string = args.path;
|
||||
let importer: string = args.importer;
|
||||
let namespace: string = args.namespace;
|
||||
let resolveDir: string = args.resolveDir;
|
||||
if (Math.random()) return
|
||||
if (Math.random()) return {}
|
||||
return {
|
||||
pluginName: '',
|
||||
errors: [
|
||||
{},
|
||||
{text: ''},
|
||||
{text: '', location: {}},
|
||||
{location: {file: '', line: 0}},
|
||||
{location: {file: '', namespace: '', line: 0, column: 0, length: 0, lineText: ''}},
|
||||
],
|
||||
warnings: [
|
||||
{},
|
||||
{text: ''},
|
||||
{text: '', location: {}},
|
||||
{location: {file: '', line: 0}},
|
||||
{location: {file: '', namespace: '', line: 0, column: 0, length: 0, lineText: ''}},
|
||||
],
|
||||
path: '',
|
||||
external: true,
|
||||
namespace: '',
|
||||
suffix: '',
|
||||
}
|
||||
})
|
||||
build.onLoad({filter: /./, namespace: ''}, args => {
|
||||
let path: string = args.path;
|
||||
let namespace: string = args.namespace;
|
||||
let suffix: string = args.suffix;
|
||||
if (Math.random()) return
|
||||
if (Math.random()) return {}
|
||||
return {
|
||||
pluginName: '',
|
||||
errors: [
|
||||
{},
|
||||
{text: ''},
|
||||
{text: '', location: {}},
|
||||
{location: {file: '', line: 0}},
|
||||
{location: {file: '', namespace: '', line: 0, column: 0, length: 0, lineText: ''}},
|
||||
],
|
||||
warnings: [
|
||||
{},
|
||||
{text: ''},
|
||||
{text: '', location: {}},
|
||||
{location: {file: '', line: 0}},
|
||||
{location: {file: '', namespace: '', line: 0, column: 0, length: 0, lineText: ''}},
|
||||
],
|
||||
contents: '',
|
||||
resolveDir: '',
|
||||
loader: 'ts',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
],
|
||||
}).then(result => {
|
||||
if (result.outputFiles !== undefined) {
|
||||
for (let file of result.outputFiles) {
|
||||
let path: string = file.path
|
||||
let bytes: Uint8Array = file.contents
|
||||
}
|
||||
}
|
||||
for (let msg of result.warnings) {
|
||||
let text: string = msg.text
|
||||
if (msg.location !== null) {
|
||||
let file: string = msg.location.file;
|
||||
let namespace: string = msg.location.namespace;
|
||||
let line: number = msg.location.line;
|
||||
let column: number = msg.location.column;
|
||||
let length: number = msg.location.length;
|
||||
let lineText: string = msg.location.lineText;
|
||||
}
|
||||
}
|
||||
})
|
||||
`,
|
||||
objectEntryPointBuild: `
|
||||
export {}
|
||||
import {build} from 'esbuild'
|
||||
build({
|
||||
entryPoints: { x: 'x', y: 'z' },
|
||||
})
|
||||
`,
|
||||
mixedEntryPointBuild: `
|
||||
export {}
|
||||
import {build} from 'esbuild'
|
||||
build({
|
||||
entryPoints: ['x', { in: 'y', out: 'z' }],
|
||||
})
|
||||
`,
|
||||
}
|
||||
|
||||
const testsWithErrors = {
|
||||
badBuildRequire_invalidOption: `
|
||||
export {}
|
||||
import esbuild = require('esbuild')
|
||||
esbuild.build({ invalidOption: true })
|
||||
`,
|
||||
badBuildSyncRequire_invalidOption: `
|
||||
export {}
|
||||
import esbuild = require('esbuild')
|
||||
esbuild.buildSync({ invalidOption: true })
|
||||
`,
|
||||
badBuildImport_invalidOption: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({ invalidOption: true })
|
||||
`,
|
||||
badBuildSyncImport_invalidOption: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({ invalidOption: true })
|
||||
`,
|
||||
badTransformRequire_invalidOption: `
|
||||
export {}
|
||||
import esbuild = require('esbuild')
|
||||
esbuild.transform('', { invalidOption: true })
|
||||
`,
|
||||
badTransformSyncRequire_invalidOption: `
|
||||
export {}
|
||||
import esbuild = require('esbuild')
|
||||
esbuild.transformSync('', { invalidOption: true })
|
||||
`,
|
||||
badTransformImport_invalidOption: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transform('', { invalidOption: true })
|
||||
`,
|
||||
badTransformSyncImport_invalidOption: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transformSync('', { invalidOption: true })
|
||||
`,
|
||||
|
||||
// mangleCache
|
||||
mangleCacheBuild_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({}).then(result => result.mangleCache['x'])
|
||||
`,
|
||||
mangleCacheBuildSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.buildSync({}).mangleCache['x']
|
||||
`,
|
||||
mangleCacheTransform_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transform('', {}).then(result => result.mangleCache['x'])
|
||||
`,
|
||||
mangleCacheTransformSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transformSync('', {}).mangleCache['x']
|
||||
`,
|
||||
|
||||
// legalComments
|
||||
legalCommentsTransform_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transform('', { legalComments: 'eof' }).then(result => result.legalComments.length)
|
||||
`,
|
||||
legalCommentsTransformSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.transformSync('', { legalComments: 'eof' }).legalComments.length
|
||||
`,
|
||||
|
||||
// outputFiles
|
||||
outputFilesBuild_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({}).then(result => result.outputFiles[0])
|
||||
`,
|
||||
outputFilesBuildSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.buildSync({}).outputFiles[0]
|
||||
`,
|
||||
outputFilesWriteTrueBuild_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({ write: true }).then(result => result.outputFiles[0])
|
||||
`,
|
||||
outputFilesWriteTrueBuildSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.buildSync({ write: true }).outputFiles[0]
|
||||
`,
|
||||
|
||||
// metafile
|
||||
metafileBuild_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({}).then(result => esbuild.analyzeMetafile(result.metafile))
|
||||
`,
|
||||
metafileBuildSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.analyzeMetafile(esbuild.buildSync({}).metafile)
|
||||
`,
|
||||
metafileFalseBuild_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.build({ metafile: false }).then(result => esbuild.analyzeMetafile(result.metafile))
|
||||
`,
|
||||
metafileFalseBuildSync_undefined: `
|
||||
import * as esbuild from 'esbuild'
|
||||
esbuild.analyzeMetafile(esbuild.buildSync({ metafile: false }).metafile)
|
||||
`,
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let testDir = path.join(__dirname, '.ts-types-test')
|
||||
removeRecursiveSync(testDir)
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
|
||||
const types = fs.readFileSync(path.join(__dirname, '..', 'lib', 'shared', 'types.ts'), 'utf8')
|
||||
const tsc = path.join(__dirname, 'node_modules', 'typescript', 'lib', 'tsc.js')
|
||||
const esbuild_d_ts = path.join(testDir, 'node_modules', 'esbuild', 'index.d.ts')
|
||||
fs.mkdirSync(path.dirname(esbuild_d_ts), { recursive: true })
|
||||
fs.writeFileSync(esbuild_d_ts, types)
|
||||
let allTestsPassed = true
|
||||
|
||||
// Check tests without errors
|
||||
if (allTestsPassed) {
|
||||
const dir = path.join(testDir, 'without-errors')
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(path.join(dir, 'tsconfig.json'), JSON.stringify(tsconfigJson))
|
||||
for (const name in testsWithoutErrors) {
|
||||
const input = path.join(dir, name + '.ts')
|
||||
fs.writeFileSync(input, testsWithoutErrors[name])
|
||||
}
|
||||
allTestsPassed &&= await new Promise(resolve => {
|
||||
const child = child_process.spawn('node', [tsc, '--project', '.'], { cwd: dir, stdio: 'inherit' })
|
||||
child.on('close', exitCode => resolve(exitCode === 0))
|
||||
})
|
||||
}
|
||||
|
||||
// Check tests with errors
|
||||
if (allTestsPassed) {
|
||||
const dir = path.join(testDir, 'with-errors')
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(path.join(dir, 'tsconfig.json'), JSON.stringify(tsconfigJson))
|
||||
for (const name in testsWithErrors) {
|
||||
const input = path.join(dir, name.split('_')[0] + '.ts')
|
||||
fs.writeFileSync(input, testsWithErrors[name])
|
||||
}
|
||||
try {
|
||||
child_process.execFileSync('node', [tsc, '--project', '.'], { cwd: dir })
|
||||
throw new Error('Expected an error to be generated')
|
||||
} catch (err) {
|
||||
const stdout = err.stdout.toString()
|
||||
const lines = stdout.split('\n')
|
||||
next: for (const name in testsWithErrors) {
|
||||
const fileName = name.split('_')[0]
|
||||
const expectedText = name.split('_')[1]
|
||||
for (const line of lines) {
|
||||
if (line.includes(fileName)) {
|
||||
if (line.includes(expectedText)) {
|
||||
console.log(`\x1B[32mSUCCESS:\x1B[0m ${line}`)
|
||||
} else {
|
||||
console.log(`\x1B[31mFAILURE: ${line}\x1B[0m`)
|
||||
allTestsPassed = false
|
||||
}
|
||||
continue next
|
||||
}
|
||||
}
|
||||
console.log(`\x1B[31mFAILURE:\x1B[0m ${name}: Could not find expected error in output from "tsc":`)
|
||||
process.stdout.write(stdout)
|
||||
allTestsPassed = false
|
||||
break next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allTestsPassed) {
|
||||
console.error(`❌ typescript type tests failed`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`✅ typescript type tests passed`)
|
||||
removeRecursiveSync(testDir)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
setTimeout(() => {
|
||||
throw e
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext", // Fix "Symbol.metadata" errors in decorator tests
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
const { installForTests } = require('./esbuild');
|
||||
const childProcess = require('child_process');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const repoDir = path.dirname(__dirname);
|
||||
const testDir = path.join(repoDir, 'scripts', '.uglify-tests');
|
||||
const uglifyDir = path.join(repoDir, 'demo', 'uglify');
|
||||
const SKIP = {};
|
||||
let U;
|
||||
|
||||
main().catch(e => setTimeout(() => { throw e }));
|
||||
|
||||
async function main() {
|
||||
// // Terser's stdout comparisons fail if this is true since stdout contains
|
||||
// // terminal color escape codes
|
||||
// process.stdout.isTTY = false;
|
||||
|
||||
// Make sure the tests are installed
|
||||
childProcess.execSync('make demo/uglify', { cwd: repoDir, stdio: 'pipe' });
|
||||
U = require(path.join(uglifyDir, 'test', 'node'));
|
||||
|
||||
// Create a fresh test directory
|
||||
childProcess.execSync(`rm -fr "${testDir}"`);
|
||||
fs.mkdirSync(testDir)
|
||||
|
||||
// Start the esbuild service
|
||||
const esbuild = installForTests();
|
||||
|
||||
// Find test files
|
||||
const compressDir = path.join(uglifyDir, 'test', 'compress');
|
||||
const files = fs.readdirSync(compressDir).filter(name => name.endsWith('.js'));
|
||||
|
||||
// Run all tests concurrently
|
||||
let passedTotal = 0;
|
||||
let failedTotal = 0;
|
||||
let skippedTotal = 0;
|
||||
const runTest = file => test_file(esbuild, path.join(compressDir, file))
|
||||
.then(({ passed, failed, skipped }) => {
|
||||
passedTotal += passed;
|
||||
failedTotal += failed;
|
||||
skippedTotal += skipped;
|
||||
});
|
||||
await Promise.all(files.map(runTest));
|
||||
|
||||
// Clean up test output
|
||||
childProcess.execSync(`rm -fr "${testDir}"`);
|
||||
|
||||
console.log(`${failedTotal} failed out of ${passedTotal + failedTotal}, with ${skippedTotal} skipped`);
|
||||
if (failedTotal) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function test_file(esbuild, file) {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
let skipped = 0;
|
||||
const tests = parse_test(file);
|
||||
const runTest = name => test_case(esbuild, tests[name], path.basename(file))
|
||||
.then(x => {
|
||||
if (x === SKIP) {
|
||||
skipped++;
|
||||
} else {
|
||||
passed++;
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
failed++;
|
||||
console.error(`❌ ${file}: ${name}: ${(e && e.message || e).trim()}\n`);
|
||||
pass = false;
|
||||
});
|
||||
await Promise.all(Object.keys(tests).map(runTest));
|
||||
return { passed, failed, skipped };
|
||||
}
|
||||
|
||||
// Modified from "uglify/demo/test/compress.js"
|
||||
async function test_case(esbuild, test, basename) {
|
||||
const sandbox = require(path.join(uglifyDir, 'test', 'sandbox'));
|
||||
const log = (format, args) => { throw new Error(tmpl(format, args)); };
|
||||
|
||||
var semver = require(path.join(uglifyDir, 'node_modules', 'semver'));
|
||||
|
||||
// Generate the input code
|
||||
var input = to_toplevel(test.input, test.mangle);
|
||||
var input_code = make_code(input);
|
||||
var input_formatted = make_code(test.input, {
|
||||
beautify: true,
|
||||
comments: "all",
|
||||
keep_quoted_props: true,
|
||||
quote_style: 3,
|
||||
});
|
||||
|
||||
// Make sure it's valid
|
||||
try {
|
||||
U.parse(input_code);
|
||||
} catch (ex) {
|
||||
log([
|
||||
"!!! Cannot parse input",
|
||||
"---INPUT---",
|
||||
"{input}",
|
||||
"--PARSE ERROR--",
|
||||
"{error}",
|
||||
"",
|
||||
"",
|
||||
].join("\n"), {
|
||||
input: input_formatted,
|
||||
error: ex,
|
||||
});
|
||||
}
|
||||
|
||||
// Ignore tests that no longer pass in modern versions of node. These tests
|
||||
// contain code that is now considered a syntax error. The relevant code is
|
||||
// this:
|
||||
//
|
||||
// try{throw 42}catch(a){console.log(a);function a(){}}
|
||||
//
|
||||
if (test.node_version && !semver.satisfies(process.version, test.node_version)) {
|
||||
console.error("*** skipping test %j with node_version %j", test.name, test.node_version);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
// Run esbuild as a minifier
|
||||
try {
|
||||
var { code: output } = await esbuild.transform(input_code, {
|
||||
minify: true,
|
||||
target: 'esnext',
|
||||
});
|
||||
} catch (e) {
|
||||
// These tests fail because they contain syntax errors. These test failures
|
||||
// do not indicate anything wrong with esbuild so the failures are ignored.
|
||||
// Here is one of the tests:
|
||||
//
|
||||
// try{}catch(a){const a="aa"}
|
||||
//
|
||||
if ([
|
||||
'const.js: issue_4290_1',
|
||||
'const.js: issue_4305_2',
|
||||
'const.js: retain_catch',
|
||||
'const.js: skip_braces',
|
||||
'exports.js: defaults',
|
||||
'exports.js: drop_unused',
|
||||
'exports.js: hoist_exports_1',
|
||||
'exports.js: hoist_exports_2',
|
||||
'exports.js: keep_return_values',
|
||||
'exports.js: mangle_rename',
|
||||
'exports.js: mangle',
|
||||
'exports.js: refs',
|
||||
'imports.js: issue_4708_1',
|
||||
'imports.js: issue_4708_2',
|
||||
'let.js: issue_4290_1',
|
||||
'let.js: issue_4305_2',
|
||||
'let.js: retain_catch',
|
||||
'let.js: skip_braces',
|
||||
'reduce_vars.js: defun_catch_4',
|
||||
'reduce_vars.js: defun_catch_5',
|
||||
'templates.js: malformed_evaluate_1',
|
||||
'templates.js: malformed_evaluate_2',
|
||||
'templates.js: malformed_evaluate_3',
|
||||
'varify.js: issue_4290_1_const',
|
||||
'varify.js: issue_4290_1_let',
|
||||
].indexOf(`${basename}: ${test.name}`) >= 0) {
|
||||
console.error(`*** skipping test with known syntax error: ${basename}: ${test.name}`);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
// These tests fail because esbuild supports top-level await. Technically
|
||||
// top-level await is only allowed inside a module, and can be used as a
|
||||
// normal identifier in a script. But the script/module distinction causes
|
||||
// a lot of pain due to the need to configure every single tool to say
|
||||
// whether to parse the code as a script or a module, so esbuild mostly
|
||||
// does away with the distinction and enables top-level await everywhere.
|
||||
// This means it fails these tests but the failures are unlikely to matter
|
||||
// in real-world code, so they can be ignored. Here's one test case:
|
||||
//
|
||||
// async function await(){console.log("PASS")}await();
|
||||
//
|
||||
if ([
|
||||
'awaits.js: defun_name',
|
||||
'awaits.js: drop_fname',
|
||||
'awaits.js: functions_anonymous',
|
||||
'awaits.js: functions_inner_var',
|
||||
'awaits.js: issue_4335_1',
|
||||
'awaits.js: keep_fname',
|
||||
'classes.js: await',
|
||||
].indexOf(`${basename}: ${test.name}`) >= 0) {
|
||||
console.error(`*** skipping test with top-level await as identifier: ${basename}: ${test.name}`);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
// These tests fail because esbuild makes assigning to an inlined constant
|
||||
// a compile error to avoid code with incorrect behavior. This is a limitation
|
||||
// due to esbuild's three-pass design but it shouldn't matter in practice. It
|
||||
// just means esbuild rejects bad code at compile time instead of at run time.
|
||||
if ([
|
||||
'const.js: issue_4212_1',
|
||||
'const.js: issue_4212_2',
|
||||
].indexOf(`${basename}: ${test.name}`) >= 0) {
|
||||
console.error(`*** skipping test with assignment to an inlined constant: ${basename}: ${test.name}`);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
log("!!! esbuild failed\n---INPUT---\n{input}\n---ERROR---\n{error}\n", {
|
||||
input: input_code,
|
||||
error: e && e.message || e,
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure esbuild generates valid JavaScript
|
||||
try {
|
||||
U.parse(output);
|
||||
} catch (ex) {
|
||||
log([
|
||||
"!!! Test matched expected result but cannot parse output",
|
||||
"---INPUT---",
|
||||
"{input}",
|
||||
"---OUTPUT---",
|
||||
"{output}",
|
||||
"--REPARSE ERROR--",
|
||||
"{error}",
|
||||
"",
|
||||
"",
|
||||
].join("\n"), {
|
||||
input: input_formatted,
|
||||
output: output,
|
||||
error: ex && ex.stack || ex,
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that the stdout matches our expectations
|
||||
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
|
||||
var stdout = [run_code(input_code), run_code(input_code, true)];
|
||||
var toplevel = sandbox.has_toplevel({
|
||||
compress: test.options,
|
||||
mangle: test.mangle
|
||||
});
|
||||
var actual = stdout[toplevel ? 1 : 0];
|
||||
if (test.expect_stdout === true) {
|
||||
test.expect_stdout = actual;
|
||||
}
|
||||
actual = run_code(output, toplevel);
|
||||
|
||||
// Ignore the known failures in CI, but not otherwise
|
||||
const isExpectingFailure = !process.env.CI ? false : [
|
||||
// Stdout difference
|
||||
'const.js: issue_4225',
|
||||
'const.js: issue_4229',
|
||||
'const.js: issue_4245',
|
||||
'const.js: use_before_init_3',
|
||||
'default-values.js: retain_empty_iife',
|
||||
'destructured.js: funarg_side_effects_2',
|
||||
'destructured.js: funarg_side_effects_3',
|
||||
'let.js: issue_4225',
|
||||
'let.js: issue_4229',
|
||||
'let.js: issue_4245',
|
||||
'let.js: use_before_init_3',
|
||||
|
||||
// Error difference
|
||||
'dead-code.js: dead_code_2_should_warn',
|
||||
|
||||
// These tests fail because esbuild assumes that declaring a class that
|
||||
// extends a base class has no side effects. That's not true because
|
||||
// extending a non-constructible object throws an error. But refusing to
|
||||
// tree-shake every class with a base class is not a useful thing for a
|
||||
// bundler to do. So we pretend that this edge case doesn't exist.
|
||||
'classes.js: issue_4722_1',
|
||||
'classes.js: issue_4722_2',
|
||||
'classes.js: issue_4722_3',
|
||||
].indexOf(`${basename}: ${test.name}`) >= 0
|
||||
|
||||
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
|
||||
if (isExpectingFailure) {
|
||||
console.error(`*** skipping test with known esbuild failure: ${basename}: ${test.name}`);
|
||||
return SKIP;
|
||||
}
|
||||
|
||||
log([
|
||||
"!!! failed",
|
||||
"---INPUT---",
|
||||
"{input}",
|
||||
"---EXPECTED {expected_type}---",
|
||||
"{expected}",
|
||||
"---ACTUAL {actual_type}---",
|
||||
"{actual}",
|
||||
"",
|
||||
"",
|
||||
].join("\n"), {
|
||||
input: input_formatted,
|
||||
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
|
||||
expected: test.expect_stdout,
|
||||
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
|
||||
actual: actual,
|
||||
});
|
||||
} else if (isExpectingFailure) {
|
||||
throw new Error(`UPDATE NEEDED: expected failure for ${basename}: ${test.name}, please remove this test from known failure list`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// The code below was copied verbatim from "uglify/demo/test/compress.js"
|
||||
//
|
||||
// UglifyJS is released under the BSD license:
|
||||
//
|
||||
// Copyright 2012-2019 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials
|
||||
// provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
function evaluate(code) {
|
||||
if (code instanceof U.AST_Node) code = make_code(code, { beautify: true });
|
||||
return new Function("return(" + code + ")")();
|
||||
}
|
||||
|
||||
function make_code(ast, options) {
|
||||
var stream = U.OutputStream(options);
|
||||
ast.print(stream);
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
function parse_test(file) {
|
||||
var script = fs.readFileSync(file, "utf8");
|
||||
// TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS/issues/348
|
||||
try {
|
||||
var ast = U.parse(script, {
|
||||
filename: file
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Caught error while parsing tests in " + file);
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
var tests = Object.create(null);
|
||||
var tw = new U.TreeWalker(function (node, descend) {
|
||||
if (node instanceof U.AST_LabeledStatement
|
||||
&& tw.parent() instanceof U.AST_Toplevel) {
|
||||
var name = node.label.name;
|
||||
if (name in tests) {
|
||||
throw new Error('Duplicated test name "' + name + '" in ' + file);
|
||||
}
|
||||
tests[name] = get_one_test(name, node.body);
|
||||
return true;
|
||||
}
|
||||
if (!(node instanceof U.AST_Toplevel)) croak(node);
|
||||
});
|
||||
ast.walk(tw);
|
||||
return tests;
|
||||
|
||||
function croak(node) {
|
||||
throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", {
|
||||
file: file,
|
||||
line: node.start.line,
|
||||
col: node.start.col,
|
||||
code: make_code(node, { beautify: false })
|
||||
}));
|
||||
}
|
||||
|
||||
function read_string(stat) {
|
||||
if (stat.TYPE == "SimpleStatement") {
|
||||
var body = stat.body;
|
||||
switch (body.TYPE) {
|
||||
case "String":
|
||||
return body.value;
|
||||
case "Array":
|
||||
return body.elements.map(function (element) {
|
||||
if (element.TYPE !== "String")
|
||||
throw new Error("Should be array of strings");
|
||||
return element.value;
|
||||
}).join("\n");
|
||||
}
|
||||
}
|
||||
throw new Error("Should be string or array of strings");
|
||||
}
|
||||
|
||||
function get_one_test(name, block) {
|
||||
var test = { name: name, options: {} };
|
||||
var tw = new U.TreeWalker(function (node, descend) {
|
||||
if (node instanceof U.AST_Assign) {
|
||||
if (!(node.left instanceof U.AST_SymbolRef)) {
|
||||
croak(node);
|
||||
}
|
||||
var name = node.left.name;
|
||||
test[name] = evaluate(node.right);
|
||||
return true;
|
||||
}
|
||||
if (node instanceof U.AST_LabeledStatement) {
|
||||
var label = node.label;
|
||||
assert.ok([
|
||||
"input",
|
||||
"expect",
|
||||
"expect_exact",
|
||||
"expect_warnings",
|
||||
"expect_stdout",
|
||||
"node_version",
|
||||
].indexOf(label.name) >= 0, tmpl("Unsupported label {name} [{line},{col}]", {
|
||||
name: label.name,
|
||||
line: label.start.line,
|
||||
col: label.start.col
|
||||
}));
|
||||
var stat = node.body;
|
||||
if (label.name == "expect_exact" || label.name == "node_version") {
|
||||
test[label.name] = read_string(stat);
|
||||
} else if (label.name == "expect_stdout") {
|
||||
var body = stat.body;
|
||||
if (body instanceof U.AST_Boolean) {
|
||||
test[label.name] = body.value;
|
||||
} else if (body instanceof U.AST_Call) {
|
||||
var ctor = global[body.expression.name];
|
||||
assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", {
|
||||
line: label.start.line,
|
||||
col: label.start.col
|
||||
}));
|
||||
test[label.name] = ctor.apply(null, body.args.map(function (node) {
|
||||
assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", {
|
||||
line: label.start.line,
|
||||
col: label.start.col
|
||||
}));
|
||||
return node.value;
|
||||
}));
|
||||
} else {
|
||||
test[label.name] = read_string(stat) + "\n";
|
||||
}
|
||||
} else {
|
||||
test[label.name] = stat;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
block.walk(tw);
|
||||
return test;
|
||||
}
|
||||
}
|
||||
|
||||
function run_code(code, toplevel) {
|
||||
const sandbox = require(path.join(uglifyDir, 'test', 'sandbox'));
|
||||
|
||||
var result = sandbox.run_code(code, toplevel);
|
||||
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
|
||||
}
|
||||
|
||||
function tmpl() {
|
||||
return U.string_template.apply(null, arguments);
|
||||
}
|
||||
|
||||
function to_toplevel(input, mangle_options) {
|
||||
if (!(input instanceof U.AST_BlockStatement)) throw new Error("Unsupported input syntax");
|
||||
var directive = true;
|
||||
var offset = input.start.line;
|
||||
var tokens = [];
|
||||
var toplevel = new U.AST_Toplevel(input.transform(new U.TreeTransformer(function (node) {
|
||||
if (U.push_uniq(tokens, node.start)) node.start.line -= offset;
|
||||
if (!directive || node === input) return;
|
||||
if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) {
|
||||
return new U.AST_Directive(node.body);
|
||||
} else {
|
||||
directive = false;
|
||||
}
|
||||
})));
|
||||
toplevel.figure_out_scope(mangle_options);
|
||||
return toplevel;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,390 @@
|
||||
const { removeRecursiveSync, buildWasmLib } = require('./esbuild.js');
|
||||
const child_process = require('child_process');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const tests = {
|
||||
serveTest({ testDir, esbuildPathWASM }) {
|
||||
try {
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--servedir=.',
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: 'pipe',
|
||||
cwd: testDir,
|
||||
});
|
||||
throw new Error('Expected an error to be thrown');
|
||||
} catch (err) {
|
||||
assert.strictEqual(err.stderr + '', `✘ [ERROR] The "serve" API is not supported when using WebAssembly\n\n`)
|
||||
}
|
||||
},
|
||||
|
||||
basicStdinTest({ testDir, esbuildPathWASM }) {
|
||||
const stdout = child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--format=cjs',
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export default 1+2`,
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const module = { exports: {} };
|
||||
new Function('module', 'exports', stdout)(module, module.exports);
|
||||
assert.deepStrictEqual(module.exports.default, 3);
|
||||
},
|
||||
|
||||
stdinOutfileTest({ testDir, esbuildPathWASM }) {
|
||||
const outfile = path.join(testDir, 'out.js')
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=cjs',
|
||||
'--outfile=' + outfile,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export default 1+2`,
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const exports = require(outfile);
|
||||
assert.deepStrictEqual(exports.default, 3);
|
||||
},
|
||||
|
||||
stdinStdoutUnicodeTest({ testDir, esbuildPathWASM }) {
|
||||
const stdout = child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--format=cjs',
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export default ['π', '🍕']`,
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const module = { exports: {} };
|
||||
new Function('module', 'exports', stdout)(module, module.exports);
|
||||
assert.deepStrictEqual(module.exports.default, ['π', '🍕']);
|
||||
},
|
||||
|
||||
stdinOutfileUnicodeTest({ testDir, esbuildPathWASM }) {
|
||||
const outfile = path.join(testDir, 'out.js')
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=cjs',
|
||||
'--outfile=' + outfile,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export default ['π', '🍕']`,
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const exports = require(outfile);
|
||||
assert.deepStrictEqual(exports.default, ['π', '🍕']);
|
||||
},
|
||||
|
||||
stdoutLargeTest({ testDir, esbuildPathNative, esbuildPathWASM }) {
|
||||
const entryPoint = path.join(__dirname, 'js-api-tests.js');
|
||||
|
||||
// Build with native
|
||||
const stdoutNative = child_process.execFileSync(esbuildPathNative, [
|
||||
entryPoint,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
}).toString();
|
||||
|
||||
// Build with WASM
|
||||
const stdoutWASM = child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
entryPoint,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
}).toString();
|
||||
|
||||
// Check that the output is equal
|
||||
assert.deepStrictEqual(stdoutNative.length, stdoutWASM.length);
|
||||
assert.deepStrictEqual(stdoutNative, stdoutWASM);
|
||||
},
|
||||
|
||||
outfileLargeTest({ testDir, esbuildPathNative, esbuildPathWASM }) {
|
||||
const entryPoint = path.join(__dirname, 'js-api-tests.js');
|
||||
|
||||
// Build with native
|
||||
const outfileNative = path.join(testDir, 'a.js');
|
||||
const stdoutNative = child_process.execFileSync(esbuildPathNative, [
|
||||
entryPoint,
|
||||
'--outfile=' + outfileNative,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
}).toString();
|
||||
const jsNative = fs.readFileSync(outfileNative, 'utf8');
|
||||
|
||||
// Build with WASM
|
||||
const outfileWASM = path.join(testDir, 'b.js');
|
||||
const stdoutWASM = child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
entryPoint,
|
||||
'--outfile=' + outfileWASM,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
}).toString();
|
||||
const jsWASM = fs.readFileSync(outfileWASM, 'utf8');
|
||||
|
||||
// Check that the output is equal
|
||||
assert.deepStrictEqual(jsNative.length, jsWASM.length);
|
||||
assert.deepStrictEqual(jsNative, jsWASM);
|
||||
},
|
||||
|
||||
outfileNestedTest({ testDir, esbuildPathWASM }) {
|
||||
const outfile = path.join(testDir, 'a', 'b', 'c', 'd', 'out.js');
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=cjs',
|
||||
'--outfile=' + outfile,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export default 123`,
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const exports = require(outfile);
|
||||
assert.deepStrictEqual(exports.default, 123);
|
||||
},
|
||||
|
||||
metafileNestedTest({ testDir, esbuildPathWASM }) {
|
||||
const outfile = path.join(testDir, 'out.js');
|
||||
const metafile = path.join(testDir, 'a', 'b', 'c', 'd', 'meta.json');
|
||||
const cwd = path.join(testDir, 'a', 'b')
|
||||
fs.mkdirSync(cwd, { recursive: true })
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=cjs',
|
||||
'--outfile=' + outfile,
|
||||
'--metafile=' + metafile,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd,
|
||||
input: `export default 123`,
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const exports = require(outfile);
|
||||
assert.deepStrictEqual(exports.default, 123);
|
||||
const json = JSON.parse(fs.readFileSync(metafile, 'utf8'));
|
||||
assert.deepStrictEqual(json.outputs['../../out.js'].entryPoint, '<stdin>');
|
||||
},
|
||||
|
||||
importRelativeFileTest({ testDir, esbuildPathWASM }) {
|
||||
const outfile = path.join(testDir, 'out.js')
|
||||
const packageJSON = path.join(__dirname, '..', 'npm', 'esbuild-wasm', 'package.json');
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=cjs',
|
||||
'--outfile=' + outfile,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export {default} from ` + JSON.stringify('./' + path.relative(testDir, packageJSON)),
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const exports = require(outfile);
|
||||
assert.deepStrictEqual(exports.default, require(packageJSON));
|
||||
},
|
||||
|
||||
importAbsoluteFileTest({ testDir, esbuildPathWASM }) {
|
||||
const outfile = path.join(testDir, 'out.js')
|
||||
const packageJSON = path.join(__dirname, '..', 'npm', 'esbuild-wasm', 'package.json');
|
||||
child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=cjs',
|
||||
'--outfile=' + outfile,
|
||||
'--log-level=warning',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `export {default} from ` + JSON.stringify(packageJSON),
|
||||
}).toString();
|
||||
|
||||
// Check that the bundle is valid
|
||||
const exports = require(outfile);
|
||||
assert.deepStrictEqual(exports.default, require(packageJSON));
|
||||
},
|
||||
zipFile({ testDir, esbuildPathWASM }) {
|
||||
const entry = path.join(testDir, 'entry.js')
|
||||
fs.writeFileSync(entry, `
|
||||
import foo from './test.zip/foo.js'
|
||||
import bar from './test.zip/bar/bar.js'
|
||||
|
||||
import __virtual__1 from './test.zip/__virtual__/ignored/0/foo.js'
|
||||
import __virtual__2 from './test.zip/ignored/__virtual__/ignored/1/foo.js'
|
||||
import __virtual__3 from './test.zip/__virtual__/ignored/1/test.zip/foo.js'
|
||||
|
||||
import $$virtual1 from './test.zip/$$virtual/ignored/0/foo.js'
|
||||
import $$virtual2 from './test.zip/ignored/$$virtual/ignored/1/foo.js'
|
||||
import $$virtual3 from './test.zip/$$virtual/ignored/1/test.zip/foo.js'
|
||||
|
||||
console.log({
|
||||
foo,
|
||||
bar,
|
||||
|
||||
__virtual__1,
|
||||
__virtual__2,
|
||||
__virtual__3,
|
||||
|
||||
$$virtual1,
|
||||
$$virtual2,
|
||||
$$virtual3,
|
||||
})
|
||||
`)
|
||||
|
||||
// This uses the real file system instead of the mock file system so that
|
||||
// we can check that everything works as expected on Windows, which is not
|
||||
// a POSIX environment.
|
||||
fs.writeFileSync(path.join(testDir, 'test.zip'), Buffer.from(
|
||||
`UEsDBAoAAgAAAG1qCFUSAXosFQAAABUAAAAGABwAZm9vLmpzVVQJAAOeRfFioEXxYnV4C` +
|
||||
`wABBPUBAAAEFAAAAGV4cG9ydCBkZWZhdWx0ICdmb28nClBLAwQKAAIAAABzaghVwuDbLR` +
|
||||
`UAAAAVAAAACgAcAGJhci9iYXIuanNVVAkAA6lF8WKrRfFidXgLAAEE9QEAAAQUAAAAZXh` +
|
||||
`wb3J0IGRlZmF1bHQgJ2JhcicKUEsBAh4DCgACAAAAbWoIVRIBeiwVAAAAFQAAAAYAGAAA` +
|
||||
`AAAAAQAAAKSBAAAAAGZvby5qc1VUBQADnkXxYnV4CwABBPUBAAAEFAAAAFBLAQIeAwoAA` +
|
||||
`gAAAHNqCFXC4NstFQAAABUAAAAKABgAAAAAAAEAAACkgVUAAABiYXIvYmFyLmpzVVQFAA` +
|
||||
`OpRfFidXgLAAEE9QEAAAQUAAAAUEsFBgAAAAACAAIAnAAAAK4AAAAAAA==`, 'base64'))
|
||||
|
||||
const stdout = child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
entry,
|
||||
], {
|
||||
stdio: 'pipe',
|
||||
cwd: testDir,
|
||||
}).toString();
|
||||
|
||||
assert.strictEqual(stdout, `(() => {
|
||||
// test.zip/foo.js
|
||||
var foo_default = "foo";
|
||||
|
||||
// test.zip/bar/bar.js
|
||||
var bar_default = "bar";
|
||||
|
||||
// test.zip/__virtual__/ignored/0/foo.js
|
||||
var foo_default2 = "foo";
|
||||
|
||||
// test.zip/ignored/__virtual__/ignored/1/foo.js
|
||||
var foo_default3 = "foo";
|
||||
|
||||
// test.zip/__virtual__/ignored/1/test.zip/foo.js
|
||||
var foo_default4 = "foo";
|
||||
|
||||
// test.zip/$$virtual/ignored/0/foo.js
|
||||
var foo_default5 = "foo";
|
||||
|
||||
// test.zip/ignored/$$virtual/ignored/1/foo.js
|
||||
var foo_default6 = "foo";
|
||||
|
||||
// test.zip/$$virtual/ignored/1/test.zip/foo.js
|
||||
var foo_default7 = "foo";
|
||||
|
||||
// entry.js
|
||||
console.log({
|
||||
foo: foo_default,
|
||||
bar: bar_default,
|
||||
__virtual__1: foo_default2,
|
||||
__virtual__2: foo_default3,
|
||||
__virtual__3: foo_default4,
|
||||
$$virtual1: foo_default5,
|
||||
$$virtual2: foo_default6,
|
||||
$$virtual3: foo_default7
|
||||
});
|
||||
})();
|
||||
`)
|
||||
},
|
||||
|
||||
// https://github.com/evanw/esbuild/issues/3001
|
||||
nodePathsReaddirEINVAL({ testDir, esbuildPathWASM }) {
|
||||
const libDir = path.join(testDir, 'lib');
|
||||
const libFile = path.join(libDir, 'file.js');
|
||||
fs.mkdirSync(libDir, { recursive: true });
|
||||
fs.writeFileSync(libFile, 'foo()');
|
||||
const stdout = child_process.execFileSync('node', [
|
||||
esbuildPathWASM,
|
||||
'--bundle',
|
||||
'--format=esm',
|
||||
], {
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
cwd: testDir,
|
||||
input: `import "file.js"`,
|
||||
env: { ...process.env, NODE_PATH: libDir },
|
||||
}).toString();
|
||||
|
||||
assert.deepStrictEqual(stdout, '// lib/file.js\nfoo();\n');
|
||||
},
|
||||
};
|
||||
|
||||
function runTest({ testDir, esbuildPathNative, esbuildPathWASM, test }) {
|
||||
try {
|
||||
fs.mkdirSync(testDir, { recursive: true })
|
||||
test({ testDir, esbuildPathNative, esbuildPathWASM })
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error(`❌ ${test.name} failed: ${e && e.message || e}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Generate the WebAssembly module
|
||||
const esbuildPathNative = path.join(__dirname, '..', process.platform === 'win32' ? 'esbuild.exe' : 'esbuild');
|
||||
await buildWasmLib(esbuildPathNative);
|
||||
|
||||
const esbuildPathWASM = path.join(__dirname, '..', 'npm', 'esbuild-wasm', 'bin', 'esbuild');
|
||||
const testDir = path.join(__dirname, '.wasm-tests')
|
||||
|
||||
// Run all tests in serial because WebAssembly compilation is a CPU hog
|
||||
let allTestsPassed = true;
|
||||
for (const test in tests) {
|
||||
if (!runTest({
|
||||
testDir: path.join(testDir, test),
|
||||
test: tests[test],
|
||||
esbuildPathNative,
|
||||
esbuildPathWASM,
|
||||
})) {
|
||||
allTestsPassed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allTestsPassed) {
|
||||
console.error(`❌ wasm-tests failed`)
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log(`✅ wasm-tests passed`)
|
||||
removeRecursiveSync(testDir)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(e => setTimeout(() => { throw e }))
|
||||
Reference in New Issue
Block a user