diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..957c9c8 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "useTabs": true +} + + diff --git a/README.md b/README.md index c49b3d9..c127364 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,15 @@ npm install -g wasm-feature-detect ```html ``` @@ -34,8 +34,8 @@ npm install -g wasm-feature-detect ```html ``` @@ -44,7 +44,7 @@ If required, there’s also a UMD version ```html ``` diff --git a/genwebsite.cjs b/genwebsite.cjs index fd52528..1caa2ef 100644 --- a/genwebsite.cjs +++ b/genwebsite.cjs @@ -1,34 +1,34 @@ const hljs = require("highlight.js"); const md = require("markdown-it")({ - highlight: function(str, lang) { - if (lang && hljs.getLanguage(lang)) { - try { - return hljs.highlight(str, { language: lang }).value; - } catch (err) { - console.err("Highlighting failed:", err); - } - } + highlight: function (str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(str, { language: lang }).value; + } catch (err) { + console.err("Highlighting failed:", err); + } + } - return ""; // use external default escaping - } + return ""; // use external default escaping + }, }); const ejs = require("ejs"); const fsp = require("fs").promises; async function init() { - const readmeSrc = await fsp.readFile("./README.md", "utf8"); - const readme = md.render(readmeSrc); + const readmeSrc = await fsp.readFile("./README.md", "utf8"); + const readme = md.render(readmeSrc); - await fsp.mkdir("website").catch(e => {}); - await fsp.copyFile("./dist/esm/index.js", "./website/wasm-feature-detect.js"); - await fsp.copyFile( - "./node_modules/highlight.js/styles/github.css", - "./website/syntax.css" - ); + await fsp.mkdir("website").catch((e) => {}); + await fsp.copyFile("./dist/esm/index.js", "./website/wasm-feature-detect.js"); + await fsp.copyFile( + "./node_modules/highlight.js/styles/github.css", + "./website/syntax.css" + ); - const template = await fsp.readFile("./website.ejs", "utf8"); - const output = ejs.render(template, { readme }); - await fsp.writeFile("./website/index.html", output); + const template = await fsp.readFile("./website.ejs", "utf8"); + const output = ejs.render(template, { readme }); + await fsp.writeFile("./website/index.html", output); } init(); diff --git a/package-lock.json b/package-lock.json index f9cae57..65b4798 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "highlight.js": "^11.7.0", "magic-string": "^0.25.4", "markdown-it": "^12.3.2", - "prettier": "^1.18.2", + "prettier": "^2.8.3", "rollup": "^3.12.1", "wabt": "^1.0.32" } @@ -562,15 +562,18 @@ } }, "node_modules/prettier": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", - "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/randombytes": { @@ -1133,9 +1136,9 @@ "dev": true }, "prettier": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", - "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true }, "randombytes": { diff --git a/package.json b/package.json index 873a961..7ea73e6 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "build": "npm run build:library && npm run build:readme && npm run build:dts && npm run fmt", "build:website": "npm run build && node genwebsite.cjs", "prepublishOnly": "npm run build", - "fmt": "prettier --write ./{,{src,rollup-plugins}/**/}*.{mjs,js,md}", - "fmt_test": "prettier --check ./{,{src,rollup-plugins}/**/}*.{mjs,js,md}", + "fmt": "prettier --no-error-on-unmatched-pattern --write ./{,src,test,rollup-plugins}/*.{mjs,cjs,js,md}", + "fmt_test": "prettier --check --no-error-on-unmatched-pattern --write ./{,src,test,rollup-plugins}/*.{mjs,cjs,js,md}", "test": "npm run fmt_test && npm run build && node --no-warnings test/index.cjs" }, "repository": "GoogleChromeLabs/wasm-feature-detect", @@ -32,7 +32,7 @@ "highlight.js": "^11.7.0", "magic-string": "^0.25.4", "markdown-it": "^12.3.2", - "prettier": "^1.18.2", + "prettier": "^2.8.3", "rollup": "^3.12.1", "wabt": "^1.0.32" } diff --git a/render-dts.mjs b/render-dts.mjs index 8c113d0..d9517f4 100644 --- a/render-dts.mjs +++ b/render-dts.mjs @@ -16,8 +16,8 @@ import { writeFileSync } from "fs"; import { plugins } from "./rollup-plugins/helpers.mjs"; writeFileSync( - "./dist/index.d.ts", - `export const\n${plugins - .map(({ name }) => `${name}: () => Promise`) - .join(",\n")};` + "./dist/index.d.ts", + `export const\n${plugins + .map(({ name }) => `${name}: () => Promise`) + .join(",\n")};` ); diff --git a/render-readme.mjs b/render-readme.mjs index 647efe8..f2c1ed0 100644 --- a/render-readme.mjs +++ b/render-readme.mjs @@ -18,27 +18,27 @@ import { gzipSync } from "zlib"; import { fileExists, plugins } from "./rollup-plugins/helpers.mjs"; async function run() { - let detectors = await Promise.all( - plugins.map(async ({ path, name: func }) => { - let index; - if (await fileExists(`${path}/module.wat`)) { - index = await fsp.readFile(`${path}/module.wat`, "utf8"); - } else if (await fileExists(`${path}/module.wast`)) { - index = await fsp.readFile(`${path}/module.wast`, "utf8"); - } else { - index = await fsp.readFile(`${path}/index.js`, "utf8"); - } - const name = (/;;\s*Name:\s*(.+)$/im.exec(index) || ["", ""])[1]; - const proposal = (/;;\s*Proposal:\s*(.+)$/im.exec(index) || ["", ""])[1]; - return { name, proposal, func }; - }) - ); - const lib = await fsp.readFile("./dist/esm/index.js"); - const gzippedLib = gzipSync(lib, { level: 9 }); - const readme = await ejs.renderFile("./README.md.ejs", { - gzippedSize: Math.round(gzippedLib.length / 10) * 10, - detectors - }); - await fsp.writeFile("./README.md", readme); + let detectors = await Promise.all( + plugins.map(async ({ path, name: func }) => { + let index; + if (await fileExists(`${path}/module.wat`)) { + index = await fsp.readFile(`${path}/module.wat`, "utf8"); + } else if (await fileExists(`${path}/module.wast`)) { + index = await fsp.readFile(`${path}/module.wast`, "utf8"); + } else { + index = await fsp.readFile(`${path}/index.js`, "utf8"); + } + const name = (/;;\s*Name:\s*(.+)$/im.exec(index) || ["", ""])[1]; + const proposal = (/;;\s*Proposal:\s*(.+)$/im.exec(index) || ["", ""])[1]; + return { name, proposal, func }; + }) + ); + const lib = await fsp.readFile("./dist/esm/index.js"); + const gzippedLib = gzipSync(lib, { level: 9 }); + const readme = await ejs.renderFile("./README.md.ejs", { + gzippedSize: Math.round(gzippedLib.length / 10) * 10, + detectors, + }); + await fsp.writeFile("./README.md", readme); } -run().catch(err => console.error(err)); +run().catch((err) => console.error(err)); diff --git a/rollup-plugins/export-in-place.js b/rollup-plugins/export-in-place.js index eb6984a..5b21ff2 100644 --- a/rollup-plugins/export-in-place.js +++ b/rollup-plugins/export-in-place.js @@ -28,74 +28,74 @@ import MagicString from "magic-string"; import assert from "assert"; -export default function() { - return { - name: "export-in-place", +export default function () { + return { + name: "export-in-place", - renderChunk(code) { - try { - const ast = this.parse(code); - // Assert for the AST shape. - // 1) We should have only two declarations at top level. - assert.strictEqual( - ast.body.length, - 2, - "Bundle should have only two items at the top level." - ); - const [varDecl, exportDecl] = ast.body; - // 2) First is a variable declaration (for `const ...`). - assert.strictEqual( - varDecl.type, - "VariableDeclaration", - "First top-level item should be a variable declaration." - ); - // 3) Second is a local export list (`export { ... }`). - assert.strictEqual( - exportDecl.type, - "ExportNamedDeclaration", - "Second top-level item should be an export declaration." - ); - assert.strictEqual( - exportDecl.declaration, - null, - "Export declaration should contain a list of items." - ); - assert.strictEqual( - exportDecl.source, - null, - "Export declaration should export local items." - ); - // 4) Their counts must match. - assert.strictEqual( - varDecl.declarations.length, - exportDecl.specifiers.length, - "List of exports should contain as many items as there are variables." - ); - // Now, perform actual transformation - inline exported name back - // into each variable declarator. - const output = new MagicString(code); - const exportMap = new Map( - exportDecl.specifiers.map(spec => [ - spec.local.name, - spec.exported.name - ]) - ); - for (const { id } of varDecl.declarations) { - const exportedName = exportMap.get(id.name); - // Make sure we're exporting only declared vars. - assert( - exportedName, - `Export declaration for ${id.name} does not match a local variable.` - ); - exportMap.delete(id.name); - output.overwrite(id.start, id.end, exportedName); - } - // Finally, prepend `export ` right before `const ...` and return it. - return `export ${output.slice(varDecl.start, varDecl.end)}`; - } catch (e) { - console.warn("Could not inline exports:", e); - return code; - } - } - }; + renderChunk(code) { + try { + const ast = this.parse(code); + // Assert for the AST shape. + // 1) We should have only two declarations at top level. + assert.strictEqual( + ast.body.length, + 2, + "Bundle should have only two items at the top level." + ); + const [varDecl, exportDecl] = ast.body; + // 2) First is a variable declaration (for `const ...`). + assert.strictEqual( + varDecl.type, + "VariableDeclaration", + "First top-level item should be a variable declaration." + ); + // 3) Second is a local export list (`export { ... }`). + assert.strictEqual( + exportDecl.type, + "ExportNamedDeclaration", + "Second top-level item should be an export declaration." + ); + assert.strictEqual( + exportDecl.declaration, + null, + "Export declaration should contain a list of items." + ); + assert.strictEqual( + exportDecl.source, + null, + "Export declaration should export local items." + ); + // 4) Their counts must match. + assert.strictEqual( + varDecl.declarations.length, + exportDecl.specifiers.length, + "List of exports should contain as many items as there are variables." + ); + // Now, perform actual transformation - inline exported name back + // into each variable declarator. + const output = new MagicString(code); + const exportMap = new Map( + exportDecl.specifiers.map((spec) => [ + spec.local.name, + spec.exported.name, + ]) + ); + for (const { id } of varDecl.declarations) { + const exportedName = exportMap.get(id.name); + // Make sure we're exporting only declared vars. + assert( + exportedName, + `Export declaration for ${id.name} does not match a local variable.` + ); + exportMap.delete(id.name); + output.overwrite(id.start, id.end, exportedName); + } + // Finally, prepend `export ` right before `const ...` and return it. + return `export ${output.slice(varDecl.start, varDecl.end)}`; + } catch (e) { + console.warn("Could not inline exports:", e); + return code; + } + }, + }; } diff --git a/rollup-plugins/helpers.mjs b/rollup-plugins/helpers.mjs index 89f8b86..02850fe 100644 --- a/rollup-plugins/helpers.mjs +++ b/rollup-plugins/helpers.mjs @@ -19,38 +19,38 @@ import { resolve } from "path"; const wabt = initWabt(); export async function compileWat(watPath, features = []) { - const watSource = await fsp.readFile(watPath, "utf-8"); - const module = (await wabt).parseWat( - watPath, - watSource, - Object.fromEntries(features.map(flag => [flag, true])) - ); - return module.toBinary({ canonicalize_lebs: true }).buffer; + const watSource = await fsp.readFile(watPath, "utf-8"); + const module = (await wabt).parseWat( + watPath, + watSource, + Object.fromEntries(features.map((flag) => [flag, true])) + ); + return module.toBinary({ canonicalize_lebs: true }).buffer; } export async function compileWast(wastPath, features = []) { - const wastSource = await fsp.readFile(wastPath, "utf-8"); - try { - const module = binaryen.parseText(wastSource); - module.setFeatures(binaryen.Features.All); - return module.emitBinary(); - } catch (e) { - throw Error(`Failure parsing ${wastPath}: ${e.message}`); - } + const wastSource = await fsp.readFile(wastPath, "utf-8"); + try { + const module = binaryen.parseText(wastSource); + module.setFeatures(binaryen.Features.All); + return module.emitBinary(); + } catch (e) { + throw Error(`Failure parsing ${wastPath}: ${e.message}`); + } } export async function fileExists(path) { - try { - await fsp.stat(path); - return true; - } catch { - return false; - } + try { + await fsp.stat(path); + return true; + } catch { + return false; + } } const pluginFolder = resolve("src/detectors"); -export const plugins = readdirSync(pluginFolder).map(plugin => ({ - path: `${pluginFolder}/${plugin}`, - name: plugin.replace(/-\w/g, val => val.slice(1).toUpperCase()) +export const plugins = readdirSync(pluginFolder).map((plugin) => ({ + path: `${pluginFolder}/${plugin}`, + name: plugin.replace(/-\w/g, (val) => val.slice(1).toUpperCase()), })); diff --git a/rollup-plugins/index-generator.js b/rollup-plugins/index-generator.js index 2ec7d83..e82a70c 100644 --- a/rollup-plugins/index-generator.js +++ b/rollup-plugins/index-generator.js @@ -15,75 +15,75 @@ import { promises as fsp } from "fs"; import { compileWat, compileWast, fileExists, plugins } from "./helpers.mjs"; -export default function({ indexPath, format }) { - return { - name: "index-generator", - resolveId(id) { - if (id === indexPath) { - return id; - } - }, - async load(id) { - if (id !== indexPath) { - return; - } +export default function ({ indexPath, format }) { + return { + name: "index-generator", + resolveId(id) { + if (id === indexPath) { + return id; + } + }, + async load(id) { + if (id !== indexPath) { + return; + } - const sources = await Promise.all( - plugins.map(async ({ path, name }) => { - let moduleBytes = ""; - if (await fileExists(`${path}/module.wat`)) { - const source = await fsp.readFile(`${path}/module.wat`, "utf8"); - const features = (/;;\s*Features:\s*(.+)$/im.exec(source) || [ - "", - "" - ])[1].split(" "); - const moduleBuffer = await compileWat( - `${path}/module.wat`, - features - ); - moduleBytes = JSON.stringify([...moduleBuffer]); - } else if (await fileExists(`${path}/module.wast`)) { - const moduleBuffer = await compileWast(`${path}/module.wast`); - moduleBytes = JSON.stringify([...moduleBuffer]); - } - if (await fileExists(`${path}/index.js`)) { - const importName = `${name}_internal`; - return { - import: `import ${importName} from ${JSON.stringify( - `${path}/index.js` - )};`, - exportName: name, - exportValue: `() => ${importName}(new Uint8Array(${moduleBytes}))` - }; - } else { - return { - exportName: name, - exportValue: `async () => WebAssembly.validate(new Uint8Array(${moduleBytes}))` - }; - } - }) - ); + const sources = await Promise.all( + plugins.map(async ({ path, name }) => { + let moduleBytes = ""; + if (await fileExists(`${path}/module.wat`)) { + const source = await fsp.readFile(`${path}/module.wat`, "utf8"); + const features = (/;;\s*Features:\s*(.+)$/im.exec(source) || [ + "", + "", + ])[1].split(" "); + const moduleBuffer = await compileWat( + `${path}/module.wat`, + features + ); + moduleBytes = JSON.stringify([...moduleBuffer]); + } else if (await fileExists(`${path}/module.wast`)) { + const moduleBuffer = await compileWast(`${path}/module.wast`); + moduleBytes = JSON.stringify([...moduleBuffer]); + } + if (await fileExists(`${path}/index.js`)) { + const importName = `${name}_internal`; + return { + import: `import ${importName} from ${JSON.stringify( + `${path}/index.js` + )};`, + exportName: name, + exportValue: `() => ${importName}(new Uint8Array(${moduleBytes}))`, + }; + } else { + return { + exportName: name, + exportValue: `async () => WebAssembly.validate(new Uint8Array(${moduleBytes}))`, + }; + } + }) + ); - let exports; - if (format === "esm") { - // For ESM, just use a single `export const`. - exports = `export const ${sources - .map(s => `${s.exportName} = ${s.exportValue}`) - .join(",")}`; - } else { - // For CJS / UMD it's more optimal size-wise to export everything as a single object. - exports = `export default {${sources - .map(s => `${s.exportName}: ${s.exportValue}`) - .join(",")}}`; - } + let exports; + if (format === "esm") { + // For ESM, just use a single `export const`. + exports = `export const ${sources + .map((s) => `${s.exportName} = ${s.exportValue}`) + .join(",")}`; + } else { + // For CJS / UMD it's more optimal size-wise to export everything as a single object. + exports = `export default {${sources + .map((s) => `${s.exportName}: ${s.exportValue}`) + .join(",")}}`; + } - return ` + return ` ${sources - .map(s => s.import) - .filter(Boolean) - .join("\n")} + .map((s) => s.import) + .filter(Boolean) + .join("\n")} ${exports} `; - } - }; + }, + }; } diff --git a/rollup-plugins/size-printer.js b/rollup-plugins/size-printer.js index 9f3a326..3889b2d 100644 --- a/rollup-plugins/size-printer.js +++ b/rollup-plugins/size-printer.js @@ -1,13 +1,13 @@ import { gzipSync } from "zlib"; export default function sizePrinter() { - return { - name: "size-printer", - renderChunk(rawSource, chunk, outputOptions) { - const buffer = Buffer.from(rawSource + "\n"); - const gzipped = gzipSync(buffer, { level: 9 }); - console.log("gzipped size:", gzipped.length); - console.log("raw size:", buffer.length); - } - }; + return { + name: "size-printer", + renderChunk(rawSource, chunk, outputOptions) { + const buffer = Buffer.from(rawSource + "\n"); + const gzipped = gzipSync(buffer, { level: 9 }); + console.log("gzipped size:", gzipped.length); + console.log("raw size:", buffer.length); + }, + }; } diff --git a/rollup.config.js b/rollup.config.js index cb36caa..b80e80a 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -18,37 +18,37 @@ import indexGenerator from "./rollup-plugins/index-generator.js"; import sizePrinter from "./rollup-plugins/size-printer.js"; import exportInPlace from "./rollup-plugins/export-in-place.js"; -export default ["esm", "cjs", "umd"].map(format => ({ - input: `./src/index.js`, - output: { - file: `dist/${format}/index.${format === "cjs" ? "cjs" : "js"}`, - format, - name: "wasmFeatureDetect", - esModule: false, - generatedCode: { - constBindings: true - } - }, - plugins: [ - commonjs({ - include: ["binaryen"] - }), - indexGenerator({ - indexPath: "./src/index.js", - format - }), - ...(process.env.NO_MINIFY - ? [] - : [ - terser({ - ecma: 8, - compress: true, - mangle: { - toplevel: true - } - }), - ...(format === "esm" ? [exportInPlace()] : []) - ]), - sizePrinter() - ] +export default ["esm", "cjs", "umd"].map((format) => ({ + input: `./src/index.js`, + output: { + file: `dist/${format}/index.${format === "cjs" ? "cjs" : "js"}`, + format, + name: "wasmFeatureDetect", + esModule: false, + generatedCode: { + constBindings: true, + }, + }, + plugins: [ + commonjs({ + include: ["binaryen"], + }), + indexGenerator({ + indexPath: "./src/index.js", + format, + }), + ...(process.env.NO_MINIFY + ? [] + : [ + terser({ + ecma: 8, + compress: true, + mangle: { + toplevel: true, + }, + }), + ...(format === "esm" ? [exportInPlace()] : []), + ]), + sizePrinter(), + ], })); diff --git a/src/detectors/big-int/index.js b/src/detectors/big-int/index.js index 7832828..f2e0fa2 100644 --- a/src/detectors/big-int/index.js +++ b/src/detectors/big-int/index.js @@ -11,7 +11,7 @@ * limitations under the License. */ -export default async moduleBytes => { +export default async (moduleBytes) => { try { // This will throw a ReferenceError on platforms where BigInt is not // supported. Please do not change the test value to BigInt diff --git a/src/detectors/threads/index.js b/src/detectors/threads/index.js index 2ab58d7..8943872 100644 --- a/src/detectors/threads/index.js +++ b/src/detectors/threads/index.js @@ -11,7 +11,7 @@ * limitations under the License. */ -export default async moduleBytes => { +export default async (moduleBytes) => { try { if (typeof MessageChannel !== "undefined") { // Test for transferability of SABs (needed for Firefox) diff --git a/test/index.cjs b/test/index.cjs index af43a7b..e47bba8 100644 --- a/test/index.cjs +++ b/test/index.cjs @@ -11,33 +11,36 @@ * limitations under the License. */ -const assert = require('assert'); +const assert = require("assert"); -process.on("unhandledRejection", err => { - throw err; +process.on("unhandledRejection", (err) => { + throw err; }); // We try to import the generated module -const features = require(__dirname+'/../dist/cjs/index.cjs'); +const features = require(__dirname + "/../dist/cjs/index.cjs"); const isBoolean = function (value) { - return typeof value === 'boolean'; -} + return typeof value === "boolean"; +}; const checkFeature = async function (featureName) { - const feature = features[featureName]; - assert(feature, `The feature ${featureName} doesn't exist`); - const syncResult = feature(); - assert(syncResult instanceof Promise, `The feature ${featureName} is not an asynchronous function`); - const result = await syncResult; - console.log(`The feature ${featureName} returned: ${result}`); - assert(isBoolean(result), `The feature ${featureName} returned: ${result}`); -} + const feature = features[featureName]; + assert(feature, `The feature ${featureName} doesn't exist`); + const syncResult = feature(); + assert( + syncResult instanceof Promise, + `The feature ${featureName} is not an asynchronous function` + ); + const result = await syncResult; + console.log(`The feature ${featureName} returned: ${result}`); + assert(isBoolean(result), `The feature ${featureName} returned: ${result}`); +}; async function run() { - for(const feature of Object.keys(features)) { - await checkFeature(feature); - } + for (const feature of Object.keys(features)) { + await checkFeature(feature); + } } run();