commit 6c2183f19ca32c67d03d9975bcb8b3242d9847cc Author: Kegan Myers Date: Mon Feb 18 01:55:52 2019 -0600 initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..87e14e4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..3a33e3c --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +.npmignore +.editorconfig +.gitignore diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0503883 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "semi": true, + "requirePragma": false, + "proseWrap": "preserve", + "arrowParens": "always", + "htmlWhitespaceSensitivity": "css" +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0dd2592 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2019 Kegan Myers + +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/README.md b/README.md new file mode 100644 index 0000000..faa2630 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# @terribleplan/next-cloudflare + +This is pretty rough right now, but it works and can hopefully used to foster discussion in the next.js project around a different/better/additional serverless interface. + +## Quickstart + +1. You must be using [my fork of next.js](https://github.com/terribleplan/next.js). + 1. `git clone https://github.com/terribleplan/next.js` + 1. `yarn && pushd packages/next-server && npm link && popd && pushd packages/next && npm link && npm link next-server && popd` +1. Install this package to your project + 1. `yarn add @terribleplan/next-cloudflare` +1. Build things + 1. `npx next build && npx next-cloudflare` + +## Usage + +This package is usable as an npm module as well as a CLI. + +### CLI + +#### --input + +The project directory that houses the .next build output directory. Defaults to whatever directory the command is invoked from. + +This is passed as `cwd` to the programmatic API + +#### --output + +Where to write the output to. Defaults to `cloudflare-bundle.js` in the input directory (which defaults to the current working directory). + +### API + +#### Usage + +``` +const nextCloudflare = require('@terribleplan/next-cloudflare'); + +const bundleString = await nextCloudflare(options); + +console.log(bundleString); +``` + +#### options.cwd + +The project directory that houses the .next build output directory. Defaults to `process.cwd()`. diff --git a/bin/next-cloudflare.js b/bin/next-cloudflare.js new file mode 100755 index 0000000..4e45fe1 --- /dev/null +++ b/bin/next-cloudflare.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node +const argv = require('minimist')(process.argv.slice(2)); +const fs = require('bluebird').promisifyAll(require('fs')); +const path = require('path'); + +const nextCloudflare = require('../index.js'); + +const run = async () => { + let { input } = argv; + if (!input) { + input = path.resolve(process.cwd()); + console.log(`WARN: \`--input\` not specified, using \`${input}\``); + } + try { + await fs.accessAsync(input); + } catch (e) { + console.log(`FATAL: Unable to access directory ${input}`); + } + + let { output } = argv; + if (!output) { + output = path.resolve(input, 'cloudflare-bundle.js'); + console.log(`WARN: \`--output\` not specified, using \`${output}\``); + } + + const contents = await nextCloudflare({ + cwd: input, + }); + + if (Buffer.byteLength(contents) > 1048576) { + console.log(`WARN: Bundle size exceeds 1MB, it may not be accepted`); + } + + await fs.writeFileAsync(output, contents); +}; + +run().then( + () => { + console.log('INFO: Built cloudflare bundle ok'); + process.exit(0); + }, + (e) => { + console.log('INFO: Failed to build cloudflare bundle'); + console.log(e); + process.exit(1); + }, +); diff --git a/index.js b/index.js new file mode 100644 index 0000000..d789884 --- /dev/null +++ b/index.js @@ -0,0 +1,24 @@ +const path = require('path'); + +const compose = require('./lib/asyncCompose'); + +const generatePage = require('./stages/generatePage'); +const buildPage = require('./stages/buildPage'); +const compressPage = require('./stages/compressPage'); + +const processPath = (...parts) => ['', ...parts].join('/').replace(/\/+/g, '/'); + +const nextCloudflare = async ({ cwd = process.cwd() }) => { + const basePath = path.resolve(cwd, '.next'); + const unifiedPath = path.join(basePath, 'unified/index.js'); + + // todo: handle static files here, pass them into the main pipeline + + return await compose( + generatePage, + buildPage(cwd), + compressPage, + )(unifiedPath); +}; + +module.exports = nextCloudflare; diff --git a/lib/adapter.js b/lib/adapter.js new file mode 100644 index 0000000..80aa7c4 --- /dev/null +++ b/lib/adapter.js @@ -0,0 +1,24 @@ +const url = require('url'); + +module.exports = (page) => { + const handler = async (request) => { + const { pathname, query } = url.parse(request.url, true); + + // todo: figure out static asset serving + if (pathname.startsWith('/_next/')) { + return new Response('', { + status: 404, + }); + } + + const { status, headers, body } = await page.render(pathname, query); + + // todo: consider etag handling + return new Response(body, { + status, + headers: new Headers(headers), + }); + }; + + return handler; +}; diff --git a/lib/asyncCompose.js b/lib/asyncCompose.js new file mode 100644 index 0000000..d84867e --- /dev/null +++ b/lib/asyncCompose.js @@ -0,0 +1,13 @@ +const asyncCompose = (...funcs) => { + return async (argument) => { + let current = argument; + + for (const func of funcs) { + current = await func(current); + } + + return current; + }; +}; + +module.exports = asyncCompose; diff --git a/lib/polyfill-path.js b/lib/polyfill-path.js new file mode 100644 index 0000000..bee271c --- /dev/null +++ b/lib/polyfill-path.js @@ -0,0 +1,4 @@ +// todo: figure out at what point this isn't necessary. +// path-browserify supposedly added support for .posix at some point +const path = require('path-browserify'); +module.exports.posix = require('path-browserify'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..2f67f16 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "@terribleplan/next-cloudflare", + "version": "0.0.1", + "description": "A way to build next.js apps for cloudflare workers", + "repository": "https://github.com/terribleplan/next-cloudflare", + "author": "Kegan Myers ", + "license": "MIT", + "main": "index.js", + "bin": { + "next-cloudflare": "bin/next-cloudflare.js" + }, + "dependencies": { + "bluebird": "^3.5.3", + "glob": "^7.1.3", + "glob-promise": "^3.4.0", + "memory-fs": "^0.4.1", + "minimist": "^1.2.0", + "terser": "^3.16.1" + }, + "peerDependencies": { + "next": "^8.0.0", + "webpack": "^4.29.0", + "node-libs-browser": "^2.0.0" + } +} diff --git a/stages/buildPage.js b/stages/buildPage.js new file mode 100644 index 0000000..8ddeb07 --- /dev/null +++ b/stages/buildPage.js @@ -0,0 +1,102 @@ +const MemoryFS = require('memory-fs'); +const path = require('path'); +const realFs = require('fs'); +const pfs = require('bluebird').promisifyAll(require('fs')); +const webpack = require('webpack'); +const { NormalModuleReplacementPlugin } = webpack; + +const buildPage = (projectDir) => async (page) => { + const fs = new MemoryFS(); + const memStat = fs.stat.bind(fs); + fs.stat = function(path, ...args) { + if (path === '/entry.js') { + return memStat(path, ...args); + } + return realFs.stat(path, ...args); + }; + const memReadFile = fs.readFile.bind(fs); + fs.readFile = function(path, ...args) { + if (path === '/entry.js') { + return memReadFile(path, ...args); + } + return realFs.readFile(path, ...args); + }; + let writtenPath = null; + const memWriteFile = fs.writeFile.bind(fs); + fs.writeFile = function(path, ...args) { + writtenPath = path; + return memWriteFile(path, ...args); + }; + + fs.writeFileSync('/entry.js', page); + + const webpackConfig = { + mode: 'production', + target: 'webworker', + plugins: [ + new NormalModuleReplacementPlugin( + /^path$/, + path.resolve(__dirname, '../lib/polyfill-path.js'), + ), + ], + node: { + console: false, + global: true, + process: true, + __filename: 'mock', + __dirname: 'mock', + Buffer: true, + setImmediate: true, + dns: false, + fs: 'empty', + path: false, + crypto: 'empty', + url: true, + zlib: false, + }, + entry: '/entry.js', + output: { filename: 'output.js' }, + resolve: { modules: [] }, + }; + + webpackConfig.resolve.modules = Array.from( + new Set( + (await Promise.all( + [ + // hopefully the n_m for the project + path.resolve(__dirname, '../'), + // also hopefully the n_m for the project + path.resolve(projectDir, 'node_modules'), + // local n_m + path.resolve(__dirname, '../node_modules'), + // last resort fallback n_m for the project + path.resolve(process.cwd(), 'node_modules'), + ].map(async (path) => { + try { + await pfs.accessAsync(path); + return path; + } catch (e) {} + return false; + }), + )).filter((x) => !!x), + ), + ); + + const compiler = webpack(webpackConfig); + compiler.outputFileSystem = compiler.inputFileSystem = fs; + + return await new Promise((res, rej) => { + compiler.run((err, stats) => { + if (err) { + return rej(err); + } + if (!writtenPath) { + console.log(stats.compilation.errors); + return rej(new Error('failed to compile')); + } + res(fs.readFileSync(writtenPath).toString()); + }); + }); +}; + +module.exports = buildPage; diff --git a/stages/compressPage.js b/stages/compressPage.js new file mode 100644 index 0000000..09a2695 --- /dev/null +++ b/stages/compressPage.js @@ -0,0 +1,13 @@ +const terser = require('terser'); + +const compressPage = (page) => + terser.minify(page, { + toplevel: true, + compress: { + passes: 3, + unsafe: false, + pure_getters: true, + }, + }).code; + +module.exports = compressPage; diff --git a/stages/generatePage.js b/stages/generatePage.js new file mode 100644 index 0000000..61db9a4 --- /dev/null +++ b/stages/generatePage.js @@ -0,0 +1,16 @@ +const path = require('path'); + +const makePageString = (pagePath) => ` +const page = require(${JSON.stringify(pagePath)}); +const adapter = require(${JSON.stringify( + path.resolve(__dirname, '../lib/adapter'), +)}) + +const handler = adapter(page); + +addEventListener('fetch', (event) => { + event.respondWith(handler(event.request)); +}); +`; + +module.exports = makePageString; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..6324a6e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,213 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + +"@types/glob@*": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "11.9.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14" + integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bluebird@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +errno@^0.1.3: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + dependencies: + prr "~1.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob-promise@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" + integrity sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw== + dependencies: + "@types/glob" "*" + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +readable-stream@^2.0.1: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +source-map-support@~0.5.9: + version "0.5.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" + integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +terser@^3.16.1: + version "3.16.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493" + integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow== + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + source-map-support "~0.5.9" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=