Skip to content

Commit

Permalink
Remove JSX from output by default
Browse files Browse the repository at this point in the history
Previously, we required an extra build step to produce runnable code.
This change makes the output of MDX immediately runnable.

This drops the final requirement on Babel (Or Bublé).
Dropping Babel leads to a size and performance win for the runtime (and for
any use case that doesn’t otherwise require Babel, such as running in Node).
Of course, if people want to use the latest JavaScript features, they can still
use Babel, but it’s not *required*.

Finally, if JSX is preferred (for example, Vue treats JSX radically different
from other hyperscript interfaces and has its own JSX builders), `keepJsx` can
be set to `true`.

In short, the size breakdown for the runtime is:

* `@mdx-js/[email protected]` (last stable tag): 356.4kb
* `@mdx-js/[email protected]` (last next tag): 362.9kb
* Previous commit (on an unmaintained Bublé fork): 165kb
* This commit: 120kb (26% / 66% / 69% smaller)

Core only adds ±1kb to its bundle size, because `estree-util-build-jsx`
reuses dependencies that we already use, too.

Related to GH-1041.
Related to GH-1044.
Related to GH-1152.
  • Loading branch information
wooorm committed Dec 28, 2020
1 parent 6f3dc9c commit f262f45
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 76 deletions.
6 changes: 3 additions & 3 deletions packages/loader/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ module: {
//
{
test: /\.mdx$/,
use: ['babel-loader', '@mdx-js/loader']
use: ['@mdx-js/loader']
}
]
}
```

You’ll probably want to configure Babel to use `@babel/preset-react` or so, but
that’s not required.
You might want to add `babel-loader` there too if you have modern JS features
that you want to compile down.

All options given to `mdx-js/loader`, except for `renderer` (see below), are
passed to MDX itself:
Expand Down
24 changes: 3 additions & 21 deletions packages/loader/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,7 @@ const transform = (filePath, options) => {
rules: [
{
test: /\.mdx$/,
use: [
{
loader: 'babel-loader',
options: {
configFile: false,
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-react-jsx'
]
}
},
{loader: path.resolve(__dirname, '..'), options}
]
use: [{loader: path.resolve(__dirname, '..'), options}]
}
]
}
Expand Down Expand Up @@ -64,13 +51,8 @@ const run = value => {
// return new Function(val)().default
// Replace import/exports w/ parameters and return value.
const val = value
.replace(
/import _objectWithoutProperties from "@babel\/runtime\/helpers\/objectWithoutProperties";/,
''
)
.replace(/import _extends from "@babel\/runtime\/helpers\/extends";/, '')
.replace(/import React from 'react';/, '')
.replace(/import \{ mdx } from '@mdx-js\/react';/, '')
.replace(/import React from 'react'/, '')
.replace(/import \{mdx} from '@mdx-js\/react'/, '')
.replace(/export default/, 'return')

// eslint-disable-next-line no-new-func
Expand Down
2 changes: 1 addition & 1 deletion packages/mdx/estree-to-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const customGenerator = Object.assign({}, astring.baseGenerator, {
})

function estreeToJs(estree) {
return astring.generate(estree, {generator: customGenerator})
return astring.generate(estree, {generator: customGenerator, comments: true})
}

// `attr="something"`
Expand Down
12 changes: 4 additions & 8 deletions packages/mdx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ const minifyWhitespace = require('rehype-minify-whitespace')
const mdxAstToMdxHast = require('./mdx-ast-to-mdx-hast')
const mdxHastToJsx = require('./mdx-hast-to-jsx')

const pragma = `/* @jsxRuntime classic */
/* @jsx mdx */
/* @jsxFrag mdx.Fragment */`

function createMdxAstCompiler(options = {}) {
return unified()
.use(remarkParse)
Expand Down Expand Up @@ -37,13 +33,13 @@ function createConfig(mdx, options) {
}

function sync(mdx, options = {}) {
const file = createCompiler(options).processSync(createConfig(mdx, options))
return pragma + '\n' + String(file)
return String(createCompiler(options).processSync(createConfig(mdx, options)))
}

async function compile(mdx, options = {}) {
const file = await createCompiler(options).process(createConfig(mdx, options))
return pragma + '\n' + String(file)
return String(
await createCompiler(options).process(createConfig(mdx, options))
)
}

module.exports = compile
Expand Down
13 changes: 13 additions & 0 deletions packages/mdx/mdx-hast-to-jsx.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const toEstree = require('hast-util-to-estree')
const walk = require('estree-walker').walk
const buildJsx = require('estree-util-build-jsx')
const periscopic = require('periscopic')
const estreeToJs = require('./estree-to-js')

function serializeEstree(estree, options) {
const {
// Default options
skipExport = false,
keepJsx = false,
wrapExport
} = options

Expand Down Expand Up @@ -150,6 +152,13 @@ function serializeEstree(estree, options) {
exports.push({type: 'ExportDefaultDeclaration', declaration: declaration})
}

// Add JSX pragma comments.
estree.comments.unshift(
{type: 'Block', value: '@jsxRuntime classic'},
{type: 'Block', value: '@jsx mdx'},
{type: 'Block', value: '@jsxFrag mdx.Fragment'}
)

estree.body = [
...createMakeShortcodeHelper(
magicShortcodes,
Expand All @@ -159,6 +168,10 @@ function serializeEstree(estree, options) {
...exports
]

if (!keepJsx) {
buildJsx(estree)
}

return estreeToJs(estree)
}

Expand Down
1 change: 1 addition & 0 deletions packages/mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"astring": "^1.4.0",
"detab": "^2.0.0",
"estree-walker": "^2.0.0",
"estree-util-build-jsx": "^1.0.0",
"hast-util-to-estree": "^1.1.0",
"mdast-util-to-hast": "^10.1.0",
"periscopic": "^2.0.0",
Expand Down
25 changes: 15 additions & 10 deletions packages/mdx/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const run = async (value, options = {}) => {
const {code} = await babel.transformAsync(doc, {
configFile: false,
plugins: [
'@babel/plugin-transform-react-jsx',
path.resolve(__dirname, '../../babel-plugin-remove-export-keywords')
]
})
Expand All @@ -30,13 +29,19 @@ const run = async (value, options = {}) => {
}

describe('@mdx-js/mdx', () => {
it('should generate JSX', async () => {
it('should generate JS (by default)', async () => {
const result = await mdx('Hello World')

expect(result).toMatch(/mdx\("p", null, "Hello World"\)/)
})

it('should generate JSX (`keepJsx: true`)', async () => {
const result = await mdx('Hello World', {keepJsx: true})

expect(result).toMatch(/<p>\{"Hello World"\}<\/p>/)
})

it('should generate runnable JSX', async () => {
it('should generate runnable JS', async () => {
const Content = await run('Hello World')

expect(renderToStaticMarkup(<Content />)).toEqual(
Expand Down Expand Up @@ -396,7 +401,7 @@ describe('@mdx-js/mdx', () => {
rehypePlugins: [plugin]
})

expect(result).toMatch(/export const A = \(\) => <b>!<\/b>/)
expect(result).toMatch(/export const A = \(\) => mdx\("b", null, "!"\)/)
})

it('should crash on incorrect exports', async () => {
Expand Down Expand Up @@ -711,24 +716,24 @@ describe('@mdx-js/mdx', () => {

describe('default', () => {
it('should be async', async () => {
expect(mdx('x')).resolves.toMatch(/<p>{"x"}<\/p>/)
expect(mdx('x')).resolves.toMatch(/mdx\("p", null, "x"\)/)
})

it('should support `remarkPlugins`', async () => {
expect(mdx('$x$', {remarkPlugins: [math]})).resolves.toMatch(
/className="math math-inline"/
/className: "math math-inline"/
)
})
})

describe('sync', () => {
it('should be sync', () => {
expect(mdx.sync('x')).toMatch(/<p>{"x"}<\/p>/)
expect(mdx.sync('x')).toMatch(/mdx\("p", null, "x"\)/)
})

it('should support `remarkPlugins`', () => {
expect(mdx.sync('$x$', {remarkPlugins: [math]})).toMatch(
/className="math math-inline"/
/className: "math math-inline"/
)
})
})
Expand Down Expand Up @@ -772,7 +777,7 @@ describe('createMdxAstCompiler', () => {
describe('createCompiler', () => {
it('should create a unified processor', () => {
expect(String(mdx.createCompiler().processSync('x'))).toMatch(
/<p>{"x"}<\/p>/
/mdx\("p", null, "x"\)/
)
})
})
Expand Down Expand Up @@ -817,6 +822,6 @@ describe('mdx-hast-to-jsx', () => {
const doc = unified().use(toJsx).stringify(tree)

expect(doc).toMatch(/export default MDXContent/)
expect(doc).toMatch(/<x>\{"a"}<\/x>/)
expect(doc).toMatch(/mdx\("x", null, "a"\)/)
})
})
1 change: 0 additions & 1 deletion packages/preact/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const run = async value => {
const {code} = await babelTransform(doc, {
configFile: false,
plugins: [
'@babel/plugin-transform-react-jsx',
path.resolve(__dirname, '../../babel-plugin-remove-export-keywords')
]
})
Expand Down
1 change: 0 additions & 1 deletion packages/react/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const run = async value => {
const {code} = await babelTransform(doc, {
configFile: false,
plugins: [
'@babel/plugin-transform-react-jsx',
path.resolve(__dirname, '../../babel-plugin-remove-export-keywords')
]
})
Expand Down
3 changes: 1 addition & 2 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@
},
"dependencies": {
"@mdx-js/mdx": "^2.0.0-next.8",
"@mdx-js/react": "^2.0.0-next.8",
"buble-jsx-only": "^0.19.8"
"@mdx-js/react": "^2.0.0-next.8"
},
"devDependencies": {
"microbundle": "^0.12.0",
Expand Down
7 changes: 2 additions & 5 deletions packages/runtime/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react'
import {transform} from 'buble-jsx-only'
import mdx from '@mdx-js/mdx'
import {MDXProvider, mdx as createElement} from '@mdx-js/react'

Expand All @@ -25,21 +24,19 @@ export default ({
...scope
}

const jsx = mdx
const js = mdx
.sync(children, {
remarkPlugins,
rehypePlugins,
skipExport: true
})
.trim()

const code = transform(jsx, {objectAssign: 'Object.assign'}).code

const keys = Object.keys(fullScope)
const values = Object.values(fullScope)

// eslint-disable-next-line no-new-func
const fn = new Function('React', ...keys, `${code}\n\n${suffix}`)
const fn = new Function('React', ...keys, `${js}\n\n${suffix}`)

return fn(React, ...values)
}
3 changes: 2 additions & 1 deletion packages/vue-loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ async function mdxLoader(content) {
result = await mdx(content, {
...options,
skipExport: true,
mdxFragment: false
mdxFragment: false,
keepJsx: true
})
} catch (err) {
return callback(err)
Expand Down
6 changes: 5 additions & 1 deletion packages/vue/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {MDXProvider, mdx} from '../src'

const run = async value => {
// Turn the serialized MDX code into serialized JSX…
const doc = await mdxTransform(value, {skipExport: true, mdxFragment: false})
const doc = await mdxTransform(value, {
skipExport: true,
mdxFragment: false,
keepJsx: true
})

// …and that into serialized JS.
const {code} = await babelTransform(doc, {
Expand Down
34 changes: 12 additions & 22 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4985,11 +4985,6 @@ accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"

acorn-dynamic-import@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948"
integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==

acorn-globals@^4.1.0, acorn-globals@^4.3.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7"
Expand All @@ -5006,7 +5001,7 @@ acorn-globals@^6.0.0:
acorn "^7.1.1"
acorn-walk "^7.1.1"

acorn-jsx@^5.0.0, acorn-jsx@^5.0.1, acorn-jsx@^5.2.0, acorn-jsx@^5.3.1:
acorn-jsx@^5.0.0, acorn-jsx@^5.2.0, acorn-jsx@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
Expand All @@ -5026,7 +5021,7 @@ acorn@^5.5.3:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==

acorn@^6.0.1, acorn@^6.0.4, acorn@^6.0.7, acorn@^6.1.1, acorn@^6.2.1, acorn@^6.4.1:
acorn@^6.0.1, acorn@^6.0.4, acorn@^6.0.7, acorn@^6.2.1, acorn@^6.4.1:
version "6.4.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
Expand Down Expand Up @@ -6590,19 +6585,6 @@ btoa-lite@^1.0.0:
resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc=

buble-jsx-only@^0.19.8:
version "0.19.8"
resolved "https://registry.yarnpkg.com/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz#6e3524aa0f1c523de32496ac9aceb9cc2b493867"
integrity sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA==
dependencies:
acorn "^6.1.1"
acorn-dynamic-import "^4.0.0"
acorn-jsx "^5.0.1"
chalk "^2.4.2"
magic-string "^0.25.3"
minimist "^1.2.0"
regexpu-core "^4.5.4"

[email protected]:
version "0.19.6"
resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.6.tgz#915909b6bd5b11ee03b1c885ec914a8b974d34d3"
Expand Down Expand Up @@ -10515,6 +10497,14 @@ estree-util-attach-comments@^1.0.0:
resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-1.0.0.tgz#51d280e458ce85dec0b813bd96d2ce98eae8a3f2"
integrity sha512-sL7dTwFGqzelPlB56lRZY1CC/yDxCe365WQpxNd49ispL40Yv8Tv4SmteGbvZeFwShOOVKfMlo4jrVvwoaMosA==

estree-util-build-jsx@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-1.0.0.tgz#80df2b0d8fbdfa7e2e7b16c02d007062af2f0ed0"
integrity sha512-OVzOP9kjOBO7xiN+A7mMjfJQyIxf+prnohyg1afd3sVHW1GTOY55SfyeKvPO+C0Ej7crP1NG/gFMmowxcKy6kw==
dependencies:
estree-util-is-identifier-name "^1.0.0"
estree-walker "^2.0.0"

estree-util-is-identifier-name@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-1.0.0.tgz#3f9b8ae3e9d661858752ce73450f37dca160f029"
Expand Down Expand Up @@ -16615,7 +16605,7 @@ magic-string@^0.22.4:
dependencies:
vlq "^0.2.2"

magic-string@^0.25.1, magic-string@^0.25.2, magic-string@^0.25.3:
magic-string@^0.25.1, magic-string@^0.25.2:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
Expand Down Expand Up @@ -21821,7 +21811,7 @@ regexpp@^3.0.0, regexpp@^3.1.0:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==

regexpu-core@^4.2.0, regexpu-core@^4.5.4, regexpu-core@^4.7.1:
regexpu-core@^4.2.0, regexpu-core@^4.7.1:
version "4.7.1"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
Expand Down

0 comments on commit f262f45

Please sign in to comment.