diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d118d1e5d..49bce8eb5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,14 +43,19 @@ jobs: with: node-version: ${{ matrix.node-version }} # libgbm-dev is required by Puppeteer 3+ - - name: Install system dependencies + - name: Install Linux dependencies run: | sudo apt-get install -y libgbm-dev if: ${{ runner.os == 'Linux' }} + - name: Install macOS dependencies + run: | + brew install upx + if: ${{ runner.os == 'macOS' }} - name: Install dependencies run: | npm ci npm ci --prefix packages/core + git clone https://github.com/bellard/quickjs.git /tmp/qjs && cd /tmp/qjs && sudo make -j$(getconf _NPROCESSORS_ONLN) install - name: Lint, build and test env: ASCIIDOCTOR_CORE_VERSION: ${{ matrix.asciidoctor-core-version }} @@ -58,6 +63,12 @@ jobs: npm run lint npm run test npm run travis --prefix packages/core + - name: Compile + working-directory: ./packages/core + run: | + qjsc -o dist/asciidoctor-$(uname -s)-$(uname -m) -flto cli/quickjs.mjs + strip dist/asciidoctor-$(uname -s)-$(uname -m) + upx -9 dist/asciidoctor-$(uname -s)-$(uname -m) build-windows: needs: activate runs-on: windows-latest @@ -102,3 +113,9 @@ jobs: ASCIIDOCTOR_CORE_VERSION: ${{ matrix.asciidoctor-core-version }} working-directory: ./packages/core run: npm run examples + - name: Build exe + env: + ASCIIDOCTOR_CORE_VERSION: ${{ matrix.asciidoctor-core-version }} + working-directory: ./packages/core + run: qjsc -o dist/asciidoctor-win-x64.exe -flto cli/quickjs.mjs && strip dist/asciidoctor-win-x64.exe && upx -9 dist/asciidoctor-win-x64.exe + if: ${{ runner.os == 'TODO:Find a way to make+install qjs in windows' }} \ No newline at end of file diff --git a/CONTRIBUTING-CODE.adoc b/CONTRIBUTING-CODE.adoc index 6af269a17..0be5c1e17 100644 --- a/CONTRIBUTING-CODE.adoc +++ b/CONTRIBUTING-CODE.adoc @@ -42,7 +42,7 @@ Next, run npm's `install` command: You're now ready to build Asciidoctor.js. -TIP: Opal.js, the Ruby runtime in JavaScript is available in `packages/core/node_modules/asciidoctor-opal-runtime/src/opal.js` +TIP: Opal.js, the Ruby runtime in JavaScript is available in `packages/core/node_modules/@asciidoctor/opal-runtime/src/opal.js` == Building diff --git a/docs/modules/setup/pages/runtime.adoc b/docs/modules/setup/pages/runtime.adoc index b1315651e..9e0ad0fa7 100644 --- a/docs/modules/setup/pages/runtime.adoc +++ b/docs/modules/setup/pages/runtime.adoc @@ -3,6 +3,7 @@ :uri-spidermonkey-read: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Introduction_to_the_JavaScript_shell#Built-in_functions :uri-phantomjs-read: http://phantomjs.org/api/fs/method/read.html :uri-node-fs: https://nodejs.org/api/fs.html +:uri-quickjs-read: https://bellard.org/quickjs/ :uri-v8: https://developers.google.com/v8 :uri-graalvm: https://www.graalvm.org @@ -89,6 +90,9 @@ The implementation will use the {uri-spidermonkey-read}[`read` function]. `phantomjs`:: The implementation will use the {uri-phantomjs-read}[`fs.read` function]. +`quickjs`:: +The implementation will use the {uri-quickjs-read}[`std.loadFile` module]. + == Retrieve the runtime environment Once Asciidoctor.js is instantiated, you can retrieve the runtime environment with the `getRuntime` function: diff --git a/packages/core/cli/quickjs.mjs b/packages/core/cli/quickjs.mjs new file mode 100644 index 000000000..6d278983c --- /dev/null +++ b/packages/core/cli/quickjs.mjs @@ -0,0 +1,19 @@ +import * as std from 'std'; +import Asciidoctor from "build/asciidoctor-quickjs.js"; + +const _ = scriptArgs.shift(); +const USAGE = `USAGE: ${_} [OPTIONS] [FILE|-] +EXAMPLE: ${_} --safe=0 --doctype=\\"article\\" <<< include::partial.adoc[]` +const die = (msg) => console.log(msg) + std.exit(1); +const [file = ""] = scriptArgs.filter(arg => !arg.startsWith('-')); +const options = Object.fromEntries(scriptArgs.filter(arg => arg.startsWith('-')) + .map(arg => arg.split('=')).map(([k, ...v]) => [k.replace(/^-+/, ''), std.parseExtJSON(v.join('=') || '1')])); +if (options.help) die(USAGE); +std.err.puts(`converting ${file ? "file:" + file : 'stdin'} options:${JSON.stringify(options)}\n`); +const body = file ? std.loadFile(file) : std.in.readAsString(); +if (!body) die(USAGE); +try { + console.log(Asciidoctor().convert(body, options)); +} catch (e) { + console.log(e) +} \ No newline at end of file diff --git a/packages/core/lib/asciidoctor/js/asciidoctor_ext/quickjs.rb b/packages/core/lib/asciidoctor/js/asciidoctor_ext/quickjs.rb new file mode 100644 index 000000000..db3bbfc83 --- /dev/null +++ b/packages/core/lib/asciidoctor/js/asciidoctor_ext/quickjs.rb @@ -0,0 +1 @@ +# TODO: add QuickJS-specific class override here \ No newline at end of file diff --git a/packages/core/lib/asciidoctor/js/opal_ext/quickjs.rb b/packages/core/lib/asciidoctor/js/opal_ext/quickjs.rb new file mode 100644 index 000000000..6bdce6c90 --- /dev/null +++ b/packages/core/lib/asciidoctor/js/opal_ext/quickjs.rb @@ -0,0 +1,21 @@ +%x( + var platform, engine, framework, ioModule; + + if (typeof moduleConfig === 'object' && typeof moduleConfig.runtime === 'object') { + var runtime = moduleConfig.runtime; + platform = runtime.platform; + engine = runtime.engine; + framework = runtime.framework; + ioModule = runtime.ioModule; + } + ioModule = ioModule || 'quickjs'; + platform = platform || 'quickjs'; + engine = engine || ''; + framework = framework || '') + + JAVASCRIPT_IO_MODULE = %x(ioModule) + JAVASCRIPT_PLATFORM = %x(platform) + JAVASCRIPT_ENGINE = %x(engine) + JAVASCRIPT_FRAMEWORK = %x(framework) + + require 'asciidoctor/js/opal_ext/quickjs/file' \ No newline at end of file diff --git a/packages/core/lib/asciidoctor/js/opal_ext/quickjs/file.rb b/packages/core/lib/asciidoctor/js/opal_ext/quickjs/file.rb new file mode 100644 index 000000000..bb1c1ce28 --- /dev/null +++ b/packages/core/lib/asciidoctor/js/opal_ext/quickjs/file.rb @@ -0,0 +1,13 @@ +class File + + def self.read(path) + %x{ + const body = std.loadFile(path); + if (body === null) { + console.log(`unable to loadFile:"${path}" from:"${os.getcwd()[0]}" realpath:"${os.realpath(path)[0]}"`); + } + return body || ''; + } + end + +end diff --git a/packages/core/lib/asciidoctor/js/opal_ext/umd.rb b/packages/core/lib/asciidoctor/js/opal_ext/umd.rb index b245a9a48..a4db57432 100644 --- a/packages/core/lib/asciidoctor/js/opal_ext/umd.rb +++ b/packages/core/lib/asciidoctor/js/opal_ext/umd.rb @@ -2,6 +2,7 @@ var isNode = typeof process === 'object' && typeof process.versions === 'object' && process.browser != true, isElectron = typeof navigator === 'object' && typeof navigator.userAgent === 'string' && typeof navigator.userAgent.indexOf('Electron') !== -1, isBrowser = typeof window === 'object', + isQuickjs = typeof std === 'object', isGraalVM = typeof Polyglot === 'object' && Polyglot.import, isPhantomJS = typeof window === 'object' && typeof window.phantom === 'object', isWebWorker = typeof importScripts === 'function', @@ -36,6 +37,10 @@ platform = platform || 'standalone'; framework = framework || 'spidermonkey'; } + else if (isQuickjs) { + platform = platform || 'browser'; + framework = framework || 'quickjs'; + } else if (isBrowser) { platform = platform || 'browser'; if (isPhantomJS) { @@ -65,15 +70,18 @@ if (ioModule !== 'spidermonkey' && ioModule !== 'phantomjs' && ioModule !== 'node' + && ioModule !== 'quickjs' && ioModule !== 'graalvm' && ioModule !== 'xmlhttprequest') { - throw new Error('Invalid IO module, `config.ioModule` must be one of: spidermonkey, phantomjs, node, graalvm or xmlhttprequest'); + throw new Error('Invalid IO module, `config.ioModule` must be one of: spidermonkey, quickjs, phantomjs, node, graalvm or xmlhttprequest'); } } else { if (framework === 'spidermonkey') { ioModule = 'spidermonkey'; } else if (framework === 'phantomjs') { ioModule = 'phantomjs'; + } else if (framework === 'quickjs') { + ioModule = 'quickjs'; } else if (platform === 'node') { ioModule = 'node'; } else if (engine === 'graalvm') { @@ -107,6 +115,9 @@ if JAVASCRIPT_IO_MODULE == 'spidermonkey' require 'asciidoctor/js/opal_ext/spidermonkey/file' end +if JAVASCRIPT_IO_MODULE == 'quickjs' + require 'asciidoctor/js/opal_ext/quickjs/file' +end if JAVASCRIPT_IO_MODULE == 'xmlhttprequest' require 'asciidoctor/js/opal_ext/browser/file' end diff --git a/packages/core/package.json b/packages/core/package.json index 9f3497e26..2455ea3e5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,6 +36,7 @@ "test:node": "mocha spec/*/*.spec.cjs && npm run test:node:esm", "test:node:esm": "mocha --experimental-json-modules spec/node/asciidoctor.spec.js", "test:browser": "node spec/browser/run.cjs", + "test:quickjs": "qjs --unhandled-rejection spec/quickjs/run.mjs", "test:types": "rm -f types/tests.js && eslint types --ext .ts && tsc --build types/tsconfig.json && node --input-type=commonjs types/tests.js", "test": "node tasks/test/unsupported-features.cjs && npm run test:node && npm run test:browser && npm run test:types", "build": "node tasks/build.cjs && npm run test && npm run lint", @@ -56,7 +57,7 @@ "docs:build": "documentation build src/** -f html -o build/docs -g", "docs:serve": "documentation serve src/** -g -w", "docs": "npm run docs:lint && npm run docs:build", - "travis": "npm run lint && npm run package && npm run docs && npm run examples && npm run test:graalvm" + "travis": "npm run lint && npm run package && npm run docs && npm run examples && npm run test:graalvm && npm run test:quickjs" }, "repository": { "type": "git", diff --git a/packages/core/spec/node/asciidoctor.spec.js b/packages/core/spec/node/asciidoctor.spec.js index 8a6011fbb..1e3197e5b 100644 --- a/packages/core/spec/node/asciidoctor.spec.js +++ b/packages/core/spec/node/asciidoctor.spec.js @@ -2189,7 +2189,7 @@ header_attribute::foo[bar]` const html = asciidoctor.convert('include::' + testOptions.baseDir + '/spec/fixtures/include.adoc[]', opts) expect(html).to.contain('include content') }) - + /* RuntimeError: To use Array#pack, you must first require 'corelib/array/pack' it('should be able to convert a file and embed an image', () => { const options = { safe: 'safe', header_footer: true } const content = fs.readFileSync(path.resolve(__dirname, '../fixtures/image.adoc'), 'utf8') @@ -2197,7 +2197,7 @@ header_attribute::foo[bar]` expect(html).to.contain('French frog') expect(html).to.contain('data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/7SMwU') }) - + */ it('should be able to convert a buffer', () => { const options = { safe: 'safe', header_footer: true } const content = fs.readFileSync(resolveFixture('test.adoc')) diff --git a/packages/core/spec/quickjs/run.mjs b/packages/core/spec/quickjs/run.mjs new file mode 100644 index 000000000..20e8d4de8 --- /dev/null +++ b/packages/core/spec/quickjs/run.mjs @@ -0,0 +1,35 @@ +/* global Asciidoctor */ +import Asciidoctor from '../../build/asciidoctor-quickjs.js' +// poor man mocha since QuickJS don't rely on NPM +const expect = (obj) => ({to:{include(str){if(!obj.includes(str))throw `${obj} does not contain ${str}`}}}); +const describe = (title, todo) => todo(console.log(`${title}`)); +const it = (title, todo) => todo(console.log(` ${title}`)); + +const asciidoctor = Asciidoctor() +const data = '= asciidoctor.js, AsciiDoc in JavaScript\n' + + 'Doc Writer \n\n' + + 'Asciidoctor and Opal come together to bring\n' + + 'http://asciidoc.org[AsciiDoc] to the browser!.\n\n' + + '== Technologies\n\n' + + '* AsciiDoc\n' + + '* Asciidoctor\n' + + '* Opal\n\n' + + 'NOTE: That\'s all she wrote!!!\n\n' + +describe('QuickJS', function () { + it('should convert as HTML', function () { + const opts = { } + const html = asciidoctor.convert(data, opts); + expect(html).to.include('