From 6a75a1d9b3964227269ac0cab84aa6fe28bb0e00 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 21 Oct 2024 16:38:47 +0200 Subject: [PATCH] Add `recma-minify` --- .gitignore | 1 + doc/plugins.md | 2 + package.json | 1 + packages/recma-minify/.npmrc | 2 + packages/recma-minify/index.d.ts | 38 +++++ packages/recma-minify/index.js | 2 + packages/recma-minify/lib/index.js | 50 +++++++ packages/recma-minify/license | 21 +++ packages/recma-minify/package.json | 50 +++++++ packages/recma-minify/readme.md | 219 ++++++++++++++++++++++++++++ packages/recma-minify/tsconfig.json | 3 + readme.md | 4 + test/index.js | 42 ++++++ tsconfig.json | 1 + 14 files changed, 436 insertions(+) create mode 100644 packages/recma-minify/.npmrc create mode 100644 packages/recma-minify/index.d.ts create mode 100644 packages/recma-minify/index.js create mode 100644 packages/recma-minify/lib/index.js create mode 100644 packages/recma-minify/license create mode 100644 packages/recma-minify/package.json create mode 100644 packages/recma-minify/readme.md create mode 100644 packages/recma-minify/tsconfig.json diff --git a/.gitignore b/.gitignore index 31825bc..542f5cc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ node_modules/ yarn.lock !/packages/recma-build-jsx/index.d.ts !/packages/recma-jsx/index.d.ts +!/packages/recma-minify/index.d.ts !/packages/recma-parse/index.d.ts !/packages/recma-stringify/index.d.ts !/packages/recma/index.d.ts diff --git a/doc/plugins.md b/doc/plugins.md index da1f505..371d17a 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -27,6 +27,8 @@ The list of plugins: — set the default `() => null` for missing components in MDX * [`recma-mdx-is-mdx-component`](https://github.com/remcohaszing/recma-mdx-is-mdx-component) — define an `isMdxComponent` property on MDX components +* [`recma-minify`](https://github.com/mdx-js/recma/tree/main/packages/recma-minify) + — plugin to minify code * [`recma-nextjs-static-props`](https://github.com/remcohaszing/recma-nextjs-static-props) — expose top-level identifiers in Next.js `app.js` diff --git a/package.json b/package.json index 84e3bda..c4668fa 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "workspaces": [ "packages/recma-build-jsx/", "packages/recma-jsx/", + "packages/recma-minify/", "packages/recma-parse/", "packages/recma-stringify/", "packages/recma/", diff --git a/packages/recma-minify/.npmrc b/packages/recma-minify/.npmrc new file mode 100644 index 0000000..3757b30 --- /dev/null +++ b/packages/recma-minify/.npmrc @@ -0,0 +1,2 @@ +ignore-scripts=true +package-lock=false diff --git a/packages/recma-minify/index.d.ts b/packages/recma-minify/index.d.ts new file mode 100644 index 0000000..408868f --- /dev/null +++ b/packages/recma-minify/index.d.ts @@ -0,0 +1,38 @@ +import type {MinifyOptions} from 'terser' + +/** + * Configuration. + * + * Same as `MinifyOptions` from `terser` except that you do not need to pass + * `format`, `module`, `output`, `parse`, or `sourceMap`; + * you should probably pass `compress: true`, `ecma: 2020`, + * `mangle: true`, and `toplevel: true`. + */ +export interface Options extends MinifyOptions { + /** + * Serialization options are not supported with `recma-minify`. + */ + format?: never + + /** + * The module field is not needed with `recma-minify`. + */ + module?: never + + /** + * Serialization options are not supported with `recma-minify`. + */ + output?: never + + /** + * Parse options are not supported with `recma-minify`. + */ + parse?: never + + /** + * The module field is not supported with `recma-minify`. + */ + sourceMap?: never +} + +export {default} from './lib/index.js' diff --git a/packages/recma-minify/index.js b/packages/recma-minify/index.js new file mode 100644 index 0000000..6a4c25d --- /dev/null +++ b/packages/recma-minify/index.js @@ -0,0 +1,2 @@ +// Note: types exposed from `index.d.ts`. +export {default} from './lib/index.js' diff --git a/packages/recma-minify/lib/index.js b/packages/recma-minify/lib/index.js new file mode 100644 index 0000000..6d56672 --- /dev/null +++ b/packages/recma-minify/lib/index.js @@ -0,0 +1,50 @@ +/** + * @import {Program} from 'estree' + * @import {Options} from 'recma-minify' + * @import {MinifyOptions} from 'terser' + */ + +import {minify} from 'terser' + +/** @type {Readonly} */ +const emptySettings = {} + +/** + * Plugin to minify code. + * + * @param {Readonly | null | undefined} [options] + * Configuration (optional). + * @returns + * Transform. + */ +export default function recmaMinify(options) { + const givenSettings = options || emptySettings + + /** + * @param {Program} tree + * Tree. + * @returns {Promise} + * Minified tree. + */ + return async function (tree) { + /** @type {MinifyOptions} */ + const actualSettings = { + ...givenSettings, + module: tree.sourceType === 'module', + // @ts-expect-error: `code` and `spidermonkey` do exist. + // `code` cannot be turned off (or even defined) if source maps are + // on: otherwise source maps are not generated. + format: {code: false, spidermonkey: true}, + // @ts-expect-error: `spidermonkey` is allowed. + parse: {spidermonkey: true} + } + + // @ts-expect-error: if `spidermonkey` is on, + // an AST can be passed and is returned. + const result = await minify(tree, actualSettings) + + // @ts-expect-error: if `spidermonkey` is on, + // an AST can be passed and is returned. + return result.ast + } +} diff --git a/packages/recma-minify/license b/packages/recma-minify/license new file mode 100644 index 0000000..e7a45ac --- /dev/null +++ b/packages/recma-minify/license @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (c) Titus Wormer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/recma-minify/package.json b/packages/recma-minify/package.json new file mode 100644 index 0000000..eec05bc --- /dev/null +++ b/packages/recma-minify/package.json @@ -0,0 +1,50 @@ +{ + "author": "Titus Wormer (https://wooorm.com)", + "bugs": "https://github.com/mdx-js/recma/issues", + "contributors": [ + "Titus Wormer (https://wooorm.com)" + ], + "description": "recma plugin to add support for minifying code", + "dependencies": { + "@types/estree": "^1.0.0", + "terser": "^5.0.0" + }, + "exports": "./index.js", + "files": [ + "lib/", + "index.d.ts", + "index.js" + ], + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "homepage": "https://github.com/mdx-js/recma", + "keywords": [ + "abstract", + "ast", + "compile", + "javascript", + "minify", + "plugin", + "recma-plugin", + "recma", + "syntax", + "terser", + "tree", + "unified" + ], + "license": "MIT", + "name": "recma-minify", + "repository": "https://github.com/mdx-js/recma/tree/main/packages/recma-minify", + "scripts": {}, + "sideEffects": false, + "typeCoverage": { + "atLeast": 100, + "detail": true, + "strict": true, + "ignoreCatch": true + }, + "type": "module", + "version": "1.0.0" +} diff --git a/packages/recma-minify/readme.md b/packages/recma-minify/readme.md new file mode 100644 index 0000000..34d0d61 --- /dev/null +++ b/packages/recma-minify/readme.md @@ -0,0 +1,219 @@ +# recma-minify + +[![Build][badge-build-image]][badge-build-url] +[![Coverage][badge-coverage-image]][badge-coverage-url] +[![Downloads][badge-downloads-image]][badge-downloads-url] +[![Size][badge-size-image]][badge-size-url] +[![Sponsors][badge-sponsors-image]][badge-collective-url] +[![Backers][badge-backers-image]][badge-collective-url] +[![Chat][badge-chat-image]][badge-chat-url] + +**[recma][github-recma]** plugin to minify code. + +## Contents + +* [What is this?](#what-is-this) +* [When should I use this?](#when-should-i-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`unified().use(recmaMinify[, options])`](#unifieduserecmaminify-options) + * [`Options`](#options) +* [Types](#types) +* [Compatibility](#compatibility) +* [Security](#security) +* [Contribute](#contribute) +* [License](#license) + +## What is this? + +This package is a [unified][github-unified] +([recma][github-recma]) +that minifies code with [Terser][github-terser]. + +## When should I use this? + +You can use this if you want to make code smaller. + +You can alternatively use [`terser`][github-terser] manually if you don’t use +`recma`. + +## Install + +This package is [ESM only][github-gist-esm]. +In Node.js (version 16+), +install with [npm][npm-install]: + +```sh +npm install recma-minify +``` + +In Deno with [`esm.sh`][esmsh]: + +```js +import recmaMinify from 'https://esm.sh/recma-minify@0' +``` + +In browsers with [`esm.sh`][esmsh]: + +```html + +``` + +## Use + +Say we have the following module `example.js`: + +```js +import recmaMinify from 'recma-minify' +import recmaParse from 'recma-parse' +import recmaStringify from 'recma-stringify' +import {unified} from 'unified' + +const file = await unified() + .use(recmaParse, {module: true}) + .use(recmaMinify, { + compress: true, + ecma: 2020, + mangle: true, + toplevel: true + }) + .use(recmaStringify) + .process( + 'console.log(sum(2, 3)); function sum(left, right) { return left + right }' + ) + +console.log(String(file)) +``` + +…running that with `node example.js` yields: + +```js +console.log(2 + 3); +``` + +## API + +This package exports no identifiers. +The default export is [`recmaMinify`][api-recma-minify]. + +### `unified().use(recmaMinify[, options])` + +Plugin to minify code. + +###### Parameters + +* `options` ([`Options`][api-options], optional) + — configuration + +###### Returns + +Transform ([`Transformer`][github-unified-transformer]). + +### `Options` + +Configuration (TypeScript type). + +Same as [`MinifyOptions`][github-terser-minify-options] from +[`terser`][github-terser] except that you do not need to pass +`format`, `module`, `output`, `parse`, or `sourceMap`; +you should probably pass `compress: true`, `ecma: 2020`, +`mangle: true`, and `toplevel: true`. + +## Types + +This package is fully typed with [TypeScript][]. +It exports the additional type [`Options`][api-options]. + +## Compatibility + +Projects maintained by the unified collective are compatible with maintained +versions of Node.js. + +When we cut a new major release, +we drop support for unmaintained versions of Node. +This means we try to keep the current release line, +`recma-minify@0`, +compatible with Node.js 16. + +## Security + +As **recma** works on JS and evaluating JS is unsafe, +use of recma can also be unsafe. +Do not evaluate unsafe code. + +## Contribute + +See [§ Contribute][mdxjs-contribute] on our site for ways to get started. +See [§ Support][mdxjs-support] for ways to get help. + +This project has a [code of conduct][health-coc]. +By interacting with this repository, +organization, +or community you agree to abide by its terms. + +## License + +[MIT][file-license] © [Titus Wormer][wooorm] + + + +[api-options]: #options + +[api-recma-minify]: #unifieduserecmaminify-options + +[badge-backers-image]: https://opencollective.com/unified/backers/badge.svg + +[badge-build-image]: https://github.com/mdx-js/recma/actions/workflows/main.yml/badge.svg + +[badge-build-url]: https://github.com/mdx-js/recma/actions + +[badge-collective-url]: https://opencollective.com/unified + +[badge-coverage-image]: https://img.shields.io/codecov/c/github/mdx-js/recma.svg + +[badge-coverage-url]: https://codecov.io/github/mdx-js/recma + +[badge-downloads-image]: https://img.shields.io/npm/dm/recma-jsx.svg + +[badge-downloads-url]: https://www.npmjs.com/package/recma-jsx + +[badge-size-image]: https://img.shields.io/bundlejs/size/recma-jsx + +[badge-size-url]: https://bundlejs.com/?q=recma-jsx + +[badge-sponsors-image]: https://opencollective.com/unified/sponsors/badge.svg + +[badge-chat-image]: https://img.shields.io/badge/chat-discussions-success.svg + +[badge-chat-url]: https://github.com/mdx-js/mdx/discussions + +[esmsh]: https://esm.sh + +[file-license]: license + +[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +[github-recma]: https://github.com/mdx-js/recma + +[github-terser-minify-options]: https://github.com/terser/terser#minify-options + +[github-terser]: https://github.com/terser/terser + +[github-unified-transformer]: https://github.com/unifiedjs/unified#transformer + +[github-unified]: https://github.com/unifiedjs/unified + +[health-coc]: https://github.com/mdx-js/.github/blob/main/code-of-conduct.md + +[mdxjs-contribute]: https://mdxjs.com/community/contribute/ + +[mdxjs-support]: https://mdxjs.com/community/support/ + +[npm-install]: https://docs.npmjs.com/cli/install + +[typescript]: https://www.typescriptlang.org + +[wooorm]: https://wooorm.com diff --git a/packages/recma-minify/tsconfig.json b/packages/recma-minify/tsconfig.json new file mode 100644 index 0000000..4082f16 --- /dev/null +++ b/packages/recma-minify/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/readme.md b/readme.md index 0894ed1..bbdb57c 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,8 @@ This GitHub repository is a monorepo that contains the following packages: — plugin to turn JSX into function calls * [`recma-jsx`][github-recma-jsx] — plugin to add support for JSX +* [`recma-minify`][github-recma-minify] + — plugin to minify code * [`recma-parse`][github-recma-parse] — plugin to take JS as input and turn it into a syntax tree (esast) * [`recma-stringify`][github-recma-stringify] @@ -96,6 +98,8 @@ Two good ways to find plugins: [github-recma-jsx]: https://github.com/mdx-js/recma/tree/main/packages/recma-jsx +[github-recma-minify]: https://github.com/mdx-js/recma/tree/main/packages/recma-minify + [github-recma-parse]: https://github.com/mdx-js/recma/tree/main/packages/recma-parse [github-recma-stringify]: https://github.com/mdx-js/recma/tree/main/packages/recma-stringify diff --git a/test/index.js b/test/index.js index 985ee61..b5a0c96 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ import test from 'node:test' import {visit as visitEstree} from 'estree-util-visit' import recmaBuildJsx from 'recma-build-jsx' import recmaJsx from 'recma-jsx' +import recmaMinify from 'recma-minify' import recmaParse from 'recma-parse' import recmaStringify from 'recma-stringify' import rehypeParse from 'rehype-parse' @@ -86,6 +87,47 @@ test('recma-jsx', async function (t) { }) }) +test('recma-minify', async function (t) { + await t.test( + 'should expose the public api of `recma-minify`', + async function () { + assert.deepEqual(Object.keys(await import('recma-minify')).sort(), [ + 'default' + ]) + } + ) + + await t.test('should minify w/ `recma-minify`', async function () { + const file = await unified() + .use(recmaParse, {module: true}) + .use(recmaMinify) + .use(recmaStringify) + .process('export function sum(left, right) { return left + right }') + + assert.equal( + String(file), + 'export function sum(n, r) {\n return n + r;\n}\n' + ) + }) + + await t.test('should support terser options', async function () { + const file = await unified() + .use(recmaParse) + .use(recmaMinify, { + compress: true, + ecma: 2020, + mangle: true, + toplevel: true + }) + .use(recmaStringify) + .process( + 'console.log(sum(1, 2)); function sum(left, right) { return left + right }' + ) + + assert.equal(String(file), 'console.log(1 + 2);\n') + }) +}) + test('recma-parse', async function (t) { await t.test( 'should expose the public api of `recma-parse`', diff --git a/tsconfig.json b/tsconfig.json index 8d4be53..518d5f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,7 @@ "**/*.js", "packages/recma-build-jsx/index.d.ts", "packages/recma-jsx/index.d.ts", + "packages/recma-minify/index.d.ts", "packages/recma-parse/index.d.ts", "packages/recma-stringify/index.d.ts", "packages/recma/index.d.ts",