Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QuickJS support #1735

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,32 @@ 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 }}
run: |
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
Expand Down Expand Up @@ -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' }}
2 changes: 1 addition & 1 deletion CONTRIBUTING-CODE.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions docs/modules/setup/pages/runtime.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
19 changes: 19 additions & 0 deletions packages/core/cli/quickjs.mjs
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TODO: add QuickJS-specific class override here
21 changes: 21 additions & 0 deletions packages/core/lib/asciidoctor/js/opal_ext/quickjs.rb
Original file line number Diff line number Diff line change
@@ -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'
13 changes: 13 additions & 0 deletions packages/core/lib/asciidoctor/js/opal_ext/quickjs/file.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 12 additions & 1 deletion packages/core/lib/asciidoctor/js/opal_ext/umd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/spec/node/asciidoctor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2189,15 +2189,15 @@ 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')
const html = asciidoctor.convert(content, options)
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'))
Expand Down
35 changes: 35 additions & 0 deletions packages/core/spec/quickjs/run.mjs
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>\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('<ul>');
})
it('should include stylesheet', function () {
const opts = { safe: 0, header_footer: true, attributes: { stylesheet: "spec/fixtures/css/simple.css", showtitle: true } };
const html = asciidoctor.convert(data, opts);
expect(html).to.include('4078c0');
})
it('should include file', function () {
const opts = { safe: 0 };
const html = asciidoctor.convert('include::spec/fixtures/include.adoc[]', opts)
expect(html).to.include('include content');
})
})
2 changes: 2 additions & 0 deletions packages/core/spec/share/asciidoctor-spec.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,7 @@ content`)
})

describe('Embed an image when data-uri is defined', function () {
/* RuntimeError: To use Array#pack, you must first require 'corelib/array/pack'.
it('should embed a jpeg image', function () {
const options = { safe: 'safe', attributes: { 'data-uri': true, 'allow-uri-read': true } }
const html = asciidoctor.convert(`image::${testOptions.baseDir}/spec/fixtures/images/litoria-chloris.jpg[]`, options)
Expand All @@ -1263,6 +1264,7 @@ content`)
expect(html).to.include('img src="data:image/png;base64,')
})
}
*/
it('should not throw an exception if the image does not exists', function () {
const options = { safe: 'safe', attributes: { 'data-uri': true, 'allow-uri-read': true } }
const html = asciidoctor.convert(`image::${testOptions.baseDir}/spec/fixtures/images/not_found.png[]`, options)
Expand Down
33 changes: 33 additions & 0 deletions packages/core/src/template-asciidoctor-quickjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* global Asciidoctor, ASCIIDOCTOR_JS_VERSION */
import * as std from 'std';
import * as os from 'os';

//{{opalCode}}

const __path__ = {
separator: os.platform == "win32" ? '\\' : '/',
split(path) { return path.split(this.separator); },
join(compo) { return compo.join(this.separator); },
basename(path) { return this.join(this.split(path).slice(0,-1)); },
dirname(path) { return this.split(path).pop(); },
};
const __asciidoctorDistDir__ = os.realpath(scriptArgs[0])[0].match(os.platform == "win32" ? /.*\\/ : /.*\//);

export default function (moduleConfig) {
//{{asciidoctorCode}}

//{{asciidoctorAPI}}

//{{asciidoctorVersion}}

/**
* Get Asciidoctor.js version number.
*
* @memberof Asciidoctor
* @returns {string} - returns the version number of Asciidoctor.js.
*/
Asciidoctor.prototype.getVersion = function () {
return ASCIIDOCTOR_JS_VERSION
}
return Opal.Asciidoctor
}
6 changes: 4 additions & 2 deletions packages/core/tasks/module/builder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const generateFlavors = async (asciidoctorCoreTarget, environments) => {
const opalExtData = fs.readFileSync(`build/opal-ext-${environment}.js`, 'utf8')
const asciidoctorCoreData = fs.readFileSync(asciidoctorCoreTarget, 'utf8')
let data
if (['node', 'browser'].includes(environment)) {
if (['node', 'browser', 'quickjs'].includes(environment)) {
const asciidoctorExtData = fs.readFileSync(`build/asciidoctor-ext-${environment}.js`, 'utf8')
data = opalExtData.concat('\n').concat(asciidoctorExtData).concat('\n').concat(asciidoctorCoreData)
} else {
Expand All @@ -150,6 +150,8 @@ const generateFlavors = async (asciidoctorCoreTarget, environments) => {
const target = `build/asciidoctor-${environment}.js`
if (environment === 'node') {
templateFile = 'src/template-asciidoctor-node.js'
} else if (environment === 'quickjs') {
templateFile = 'src/template-asciidoctor-quickjs.js'
} else {
templateFile = 'src/template-asciidoctor-browser.js'
}
Expand Down Expand Up @@ -216,7 +218,7 @@ module.exports = class Builder {
this.benchmarkBuildDir = path.join('build', 'benchmark')
this.examplesBuildDir = path.join('build', 'examples')
this.asciidocRepoBaseURI = 'https://raw.githubusercontent.com/asciidoc/asciidoc/d43faae38c4a8bf366dcba545971da99f2b2d625'
this.environments = ['node', 'graalvm', 'browser']
this.environments = ['node', 'graalvm', 'browser', 'quickjs']
this.asciidoctorCoreTarget = path.join('build', 'asciidoctor-core.js')
}

Expand Down