├── examples ├── with-react │ ├── .gitkeep │ ├── App.scss │ ├── index.js │ ├── Counter.js │ ├── package.json │ ├── App.js │ └── package-lock.json ├── serve-with-express │ ├── .gitkeep │ ├── README.md │ ├── client.js │ ├── package.json │ ├── server.js │ └── package-lock.json ├── serve-with-fastify │ ├── .gitkeep │ ├── client.js │ ├── README.md │ ├── package.json │ ├── server.js │ └── package-lock.json ├── lazy-loading │ ├── more.css │ ├── styles.css │ ├── more.js │ ├── package.json │ └── index.js ├── with-css-modules │ ├── jetpack.config.js │ ├── package.json │ ├── styles.css │ ├── package-lock.json │ └── index.js └── hello-world │ ├── styles.css │ ├── package.json │ └── index.js ├── test ├── fixtures │ ├── pkg-src │ │ └── src │ │ │ └── index.js │ ├── pkg-with-cjs │ │ ├── package.json │ │ ├── .browserslistrc │ │ ├── index.js │ │ └── jetpack.config.js │ ├── pkg-individual │ │ └── module.js │ ├── pkg-basic │ │ ├── styles.css │ │ ├── index.js │ │ └── package.json │ ├── pkg-with-esm │ │ ├── .browserslistrc │ │ ├── index.js │ │ └── jetpack.config.js │ ├── pkg-with-lightningcss │ │ ├── jetpack.config.js │ │ ├── unicorn.png │ │ ├── package.json │ │ ├── index.js │ │ └── styles.css │ ├── pkg-with-legacy │ │ ├── .browserslistrc │ │ ├── index.js │ │ └── jetpack.config.js │ ├── pkg-with-everything │ │ ├── more.js │ │ ├── reset.css │ │ ├── unicorn.png │ │ ├── more.css │ │ ├── package.json │ │ ├── index.js │ │ ├── jetpack.config.js │ │ └── styles.css │ ├── pkg-with-scss │ │ ├── styles.scss │ │ ├── index.js │ │ └── package.json │ └── pkg-with-config │ │ └── jetpack.config.js ├── snapshots │ └── build.test.js.snap ├── helpers │ └── rspackConfigForTests.js ├── options.test.js └── build.test.js ├── .prettierignore ├── .npmignore ├── serve.js ├── rspack.js ├── options.js ├── bin └── jetpack ├── .gitignore ├── assets ├── banner.jpg ├── banner.pxm ├── logo.png ├── logo.pxm └── architecture.png ├── .prettierrc ├── rspack.config.js ├── proxy.js ├── lib ├── rspack.assets.js ├── progress.js ├── printConfig.js ├── template.hbs ├── rspack.hot.js ├── rspack.ts.js ├── proxy.js ├── inspect.js ├── logger.js ├── clean.js ├── rspack.react.js ├── browsers.js ├── retryChunkLoadPlugin.js ├── rspack.css.js ├── rspack.js.js ├── rspack.scss.js ├── rspack.config.js ├── dev.js ├── build.js ├── cli.js ├── serve.js ├── reporter.js └── options.js ├── docs ├── recipe-06-server-side-rendering.md ├── 03-customizing-swc.md ├── 02-customizing-rspack.md ├── 05-customizing-browserslist.md ├── 08-hot-reloading.md ├── 09-comparison.md ├── 07-differential-serving.md ├── 06-workflow-and-deployment.md └── 01-configuration-options.md ├── eslint.config.js ├── .github └── workflows │ └── test.yml ├── package.json ├── README.md └── CHANGELOG.md /examples/with-react/.gitkeep: -------------------------------------------------------------------------------- 1 | .gitkeep -------------------------------------------------------------------------------- /test/fixtures/pkg-src/src/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test/fixtures 2 | examples -------------------------------------------------------------------------------- /test/fixtures/pkg-with-cjs/package.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | assets 3 | examples 4 | -------------------------------------------------------------------------------- /examples/serve-with-express/.gitkeep: -------------------------------------------------------------------------------- 1 | .gitkeep -------------------------------------------------------------------------------- /examples/serve-with-fastify/.gitkeep: -------------------------------------------------------------------------------- 1 | .gitkeep -------------------------------------------------------------------------------- /serve.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/serve') 2 | -------------------------------------------------------------------------------- /rspack.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@rspack/core') 2 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/cli').options() 2 | -------------------------------------------------------------------------------- /test/fixtures/pkg-individual/module.js: -------------------------------------------------------------------------------- 1 | module.exports = () => {} 2 | -------------------------------------------------------------------------------- /bin/jetpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli').run() 4 | -------------------------------------------------------------------------------- /examples/lazy-loading/more.css: -------------------------------------------------------------------------------- 1 | .more { 2 | color: purple; 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-react/App.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: aliceblue; 3 | } -------------------------------------------------------------------------------- /test/fixtures/pkg-basic/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: blue; 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | examples/*/dist 4 | test/fixtures/*/dist 5 | -------------------------------------------------------------------------------- /assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/assets/banner.jpg -------------------------------------------------------------------------------- /assets/banner.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/assets/banner.pxm -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/logo.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/assets/logo.pxm -------------------------------------------------------------------------------- /assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/assets/architecture.png -------------------------------------------------------------------------------- /test/fixtures/pkg-with-cjs/.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | defaults 4 | Chrome 62 -------------------------------------------------------------------------------- /test/fixtures/pkg-with-cjs/index.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return 'test '.trim() 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-esm/.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | defaults 4 | Chrome 62 -------------------------------------------------------------------------------- /test/fixtures/pkg-with-esm/index.js: -------------------------------------------------------------------------------- 1 | export const test = () => { 2 | return 'test '.trim() 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-lightningcss/jetpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | minify: false 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-legacy/.browserslistrc: -------------------------------------------------------------------------------- 1 | [modern] 2 | defaults 3 | 4 | [legacy] 5 | defaults 6 | safari 10.1 -------------------------------------------------------------------------------- /examples/with-css-modules/jetpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | css: { 3 | modules: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/snapshots/build.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/test/snapshots/build.test.js.snap -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/more.js: -------------------------------------------------------------------------------- 1 | import './more.css' 2 | 3 | console.log(window?.secret?.code ?? 'no secret code found 😔') 4 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/reset.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | padding: 0; 4 | background: red; 5 | backdrop-filter: blur(10px); 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/unicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/test/fixtures/pkg-with-everything/unicorn.png -------------------------------------------------------------------------------- /test/fixtures/pkg-with-lightningcss/unicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KidkArolis/jetpack/HEAD/test/fixtures/pkg-with-lightningcss/unicorn.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "printWidth": 120, 6 | "trailingComma": "none" 7 | } -------------------------------------------------------------------------------- /test/fixtures/pkg-with-legacy/index.js: -------------------------------------------------------------------------------- 1 | export const test = async () => 'test '.trim() 2 | export const reverse = async () => [1,2,3,42,5].toReversed() 3 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-scss/styles.scss: -------------------------------------------------------------------------------- 1 | $primary-color: blue; 2 | 3 | body { 4 | background: $primary-color; 5 | } 6 | 7 | button { 8 | appearance: none; 9 | } -------------------------------------------------------------------------------- /test/fixtures/pkg-basic/index.js: -------------------------------------------------------------------------------- 1 | require('./styles.css') 2 | 3 | function main () { 4 | document.querySelector('#root').innerHTML = 'hello world' 5 | } 6 | 7 | main() 8 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-scss/index.js: -------------------------------------------------------------------------------- 1 | require('./styles.scss') 2 | 3 | function main () { 4 | document.querySelector('#root').innerHTML = 'hello world' 5 | } 6 | 7 | main() 8 | -------------------------------------------------------------------------------- /examples/with-react/index.js: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import App from './App' 3 | 4 | const root = createRoot(document.querySelector('#root')) 5 | root.render() 6 | -------------------------------------------------------------------------------- /examples/hello-world/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: serif; 3 | font-size: 16px; 4 | } 5 | 6 | #root { 7 | margin: 100px; 8 | } 9 | 10 | h1 { 11 | color: blue; 12 | } 13 | -------------------------------------------------------------------------------- /examples/lazy-loading/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: serif; 3 | font-size: 16px; 4 | } 5 | 6 | #root { 7 | margin: 100px; 8 | } 9 | 10 | h1 { 11 | color: blue; 12 | } 13 | -------------------------------------------------------------------------------- /examples/lazy-loading/more.js: -------------------------------------------------------------------------------- 1 | import './more.css' 2 | 3 | export function more () { 4 | const p = document.createElement('p') 5 | p.className = 'more' 6 | p.innerText = 'more content' 7 | return p 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/more.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url('./unicorn.png'); 3 | background-repeat: no-repeat; 4 | background-size: cover; 5 | } 6 | 7 | .more { 8 | display: grid; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/pkg-basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg-basic", 3 | "version": "1.0.0", 4 | "private": true, 5 | "browserslist": [ 6 | "last 4 versions", 7 | "> 1%", 8 | "not dead" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/with-css-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-css-modules", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "../../bin/jetpack" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-scss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg-with-scss", 3 | "version": "1.0.0", 4 | "private": true, 5 | "browserslist": [ 6 | "last 4 versions", 7 | "> 1%", 8 | "not dead" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg-with-everything", 3 | "version": "1.0.0", 4 | "private": true, 5 | "browserslist": [ 6 | "last 4 versions", 7 | "> 1%", 8 | "not dead" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-lightningcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg-with-lightningcss", 3 | "version": "1.0.0", 4 | "private": true, 5 | "browserslist": [ 6 | "last 4 versions", 7 | "> 1%", 8 | "not dead" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /rspack.config.js: -------------------------------------------------------------------------------- 1 | const cli = require('./lib/cli') 2 | const createJetpackConfig = require('./lib/rspack.config') 3 | 4 | const options = cli.options() 5 | const rspackConfigs = createJetpackConfig(options) 6 | 7 | module.exports = rspackConfigs.modern 8 | -------------------------------------------------------------------------------- /proxy.js: -------------------------------------------------------------------------------- 1 | const options = require('./options') 2 | const proxy = require('./lib/proxy') 3 | const createLogger = require('./lib/logger') 4 | 5 | const log = createLogger(options.verbose, options.quiet) 6 | 7 | module.exports = (target) => proxy(target, log) 8 | -------------------------------------------------------------------------------- /examples/serve-with-fastify/client.js: -------------------------------------------------------------------------------- 1 | console.log('Hello') 2 | fetch('/api/data').then(res => res.text()).then(data => { 3 | console.log(data) 4 | document.querySelector('#root').innerHTML = `Fetched: ${data}` 5 | }).catch(err => { 6 | console.log(err) 7 | }) -------------------------------------------------------------------------------- /examples/serve-with-express/README.md: -------------------------------------------------------------------------------- 1 | # serve-with-express 2 | 3 | This shows how to use the `jetpack/serve` middleware in development and production when using express. 4 | 5 | $ npm install 6 | $ npm run watch 7 | $ npm run build 8 | $ npm start 9 | -------------------------------------------------------------------------------- /examples/serve-with-fastify/README.md: -------------------------------------------------------------------------------- 1 | # serve-with-fastify 2 | 3 | This shows how to use the `jetpack/serve` middleware in development and production when using fastify. 4 | 5 | $ npm install 6 | $ npm run watch 7 | $ npm run build 8 | $ npm start 9 | -------------------------------------------------------------------------------- /examples/with-css-modules/styles.css: -------------------------------------------------------------------------------- 1 | :global(html), 2 | :global(body) { 3 | font-family: sans-serif; 4 | font-size: 16px; 5 | } 6 | 7 | .container { 8 | margin: 100px; 9 | padding: 100px; 10 | border: 1px solid #eee; 11 | background: red; 12 | } 13 | -------------------------------------------------------------------------------- /examples/serve-with-express/client.js: -------------------------------------------------------------------------------- 1 | console.log('Hello') 2 | fetch('/api/data').then(res => res.text()).then(data => { 3 | console.log('Fetched:', data) 4 | document.querySelector('#root').innerHTML = `Fetched: ${data}` 5 | }).catch(err => { 6 | console.log(err) 7 | }) 8 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-lightningcss/index.js: -------------------------------------------------------------------------------- 1 | require('./styles.css') 2 | 3 | function main() { 4 | document.querySelector('#root').innerHTML = ` 5 |

Testing lightningcss compilation output

6 | ` 7 | } 8 | 9 | main() 10 | 11 | module.hot.accept() 12 | -------------------------------------------------------------------------------- /examples/hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "serve": "^14.2.4" 8 | }, 9 | "scripts": { 10 | "start": "../../bin/jetpack" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/rspack.assets.js: -------------------------------------------------------------------------------- 1 | module.exports = (config, options) => { 2 | config.output.assetModuleFilename = '[name].[hash:8][ext]' 3 | config.module.rules[0].oneOf.push({ 4 | test: /\.(svg|woff2?|ttf|eot|jpe?g|png|gif|mp4|mov|ogg|webm)(\?.*)?$/i, 5 | type: 'asset' 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /examples/lazy-loading/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy-loading", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "serve": "^14.2.4" 8 | }, 9 | "scripts": { 10 | "start": "../../bin/jetpack" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-config/jetpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 1234, 3 | entry: './app/client', 4 | title: 'testing', 5 | exec: 'node ./app/server', 6 | verbose: true, 7 | css: { 8 | features: { 9 | 'nesting-rules': true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/hello-world/index.js: -------------------------------------------------------------------------------- 1 | require('./styles.css') 2 | 3 | function main () { 4 | document.querySelector('#root').innerHTML = ` 5 |

HMR!

6 |

Hello world.

7 |

Paragraph two.

8 | ` 9 | } 10 | 11 | main() 12 | 13 | if (module.hot) { 14 | module.hot.accept() 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/index.js: -------------------------------------------------------------------------------- 1 | import img from './unicorn.png' 2 | import './styles.css' 3 | 4 | export default function main () { 5 | return
{img}
6 | } 7 | 8 | export function load () { 9 | require(['./more.js']) 10 | } 11 | 12 | setTimeout(() => { 13 | load() 14 | }, 1000) 15 | -------------------------------------------------------------------------------- /examples/with-css-modules/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-css-modules", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "with-css-modules", 9 | "version": "1.0.0", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-react/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | export default function Counter () { 4 | const [count, setCount] = useState(0) 5 | const incrementCount = () => setCount(currentCount => currentCount + 1) 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-cjs/jetpack.config.js: -------------------------------------------------------------------------------- 1 | const rspackConfigForTests = require('../../helpers/rspackConfigForTests') 2 | 3 | module.exports = { 4 | minify: false, 5 | rspack(config) { 6 | delete config.optimization.splitChunks 7 | delete config.optimization.runtimeChunk 8 | rspackConfigForTests(config) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-esm/jetpack.config.js: -------------------------------------------------------------------------------- 1 | const rspackConfigForTests = require('../../helpers/rspackConfigForTests') 2 | 3 | module.exports = { 4 | minify: false, 5 | rspack(config) { 6 | delete config.optimization.splitChunks 7 | delete config.optimization.runtimeChunk 8 | rspackConfigForTests(config) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/progress.js: -------------------------------------------------------------------------------- 1 | const rspack = require('@rspack/core') 2 | 3 | module.exports = (log) => { 4 | return new rspack.ProgressPlugin({ 5 | handler: (percentage, msg, ...args) => { 6 | log.status(`${Math.floor(percentage * 100)}%`, msg, ...args) 7 | if (percentage === 1 || (!msg && args.length === 0)) log.status('⚡️') 8 | } 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/with-css-modules/index.js: -------------------------------------------------------------------------------- 1 | import * as styles from './styles.css' 2 | 3 | function main() { 4 | document.querySelector('#root').innerHTML = ` 5 |
6 |

HMR!

7 |

Hello world.

8 |

Paragraph two.

9 |
10 | ` 11 | } 12 | 13 | main() 14 | 15 | module.hot.accept() 16 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/jetpack.config.js: -------------------------------------------------------------------------------- 1 | const rspackConfigForTests = require('../../helpers/rspackConfigForTests') 2 | 3 | module.exports = { 4 | minify: false, 5 | chunkLoadRetry: true, 6 | css: { 7 | features: { 8 | 'nesting-rules': true 9 | } 10 | }, 11 | rspack(config) { 12 | rspackConfigForTests(config) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/serve-with-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serve-with-express", 3 | "version": "1.0.0", 4 | "main": "server", 5 | "browser": "client", 6 | "license": "MIT", 7 | "dependencies": { 8 | "express": "^5.1.0" 9 | }, 10 | "scripts": { 11 | "watch": "../../bin/jetpack -x", 12 | "start": "node .", 13 | "build": "../../bin/jetpack build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/serve-with-fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serve-with-fastify", 3 | "version": "1.0.0", 4 | "main": "server", 5 | "browser": "client", 6 | "license": "MIT", 7 | "dependencies": { 8 | "fastify": "^5.4.0" 9 | }, 10 | "scripts": { 11 | "watch": "../../bin/jetpack -x", 12 | "start": "node .", 13 | "build": "../../bin/jetpack build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/with-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "../../bin/jetpack" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "react": "^19.2.0", 13 | "react-dom": "^19.2.0", 14 | "react-hot-loader": "^4.13.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-legacy/jetpack.config.js: -------------------------------------------------------------------------------- 1 | const rspackConfigForTests = require('../../helpers/rspackConfigForTests') 2 | 3 | module.exports = { 4 | minify: false, 5 | target: { 6 | modern: true, 7 | legacy: true 8 | }, 9 | rspack(config) { 10 | delete config.optimization.splitChunks 11 | delete config.optimization.runtimeChunk 12 | rspackConfigForTests(config) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/serve-with-express/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const jetpack = require('../../serve') 3 | 4 | const app = express() 5 | 6 | app.get('/api/data', (req, res) => { 7 | res.send('hello') 8 | }) 9 | 10 | app.get('/*splat', jetpack) 11 | 12 | const server = app.listen(3000) 13 | server.on('listening', function () { 14 | console.log('Running server on http://localhost:3000') 15 | }) 16 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-everything/styles.css: -------------------------------------------------------------------------------- 1 | @import './reset.css'; 2 | 3 | body { 4 | background-color: aliceblue; 5 | } 6 | 7 | .unicorn { 8 | display: flex; 9 | flex-direction: column; 10 | @media (min-width: 767px) { 11 | display: flex; 12 | flex-direction: row; 13 | } 14 | } 15 | 16 | @custom-media --wide-window (min-width: 30em); 17 | 18 | @media (--wide-window) { 19 | background: yellow; 20 | } 21 | -------------------------------------------------------------------------------- /lib/printConfig.js: -------------------------------------------------------------------------------- 1 | const util = require('util') 2 | const createRspackConfig = require('./rspack.config') 3 | 4 | module.exports = async function printConfig({ options, modern, log }) { 5 | log.info(modern ? 'Modern config\n' : 'Legacy config\n') 6 | 7 | const rspackConfigs = createRspackConfig(options, log) 8 | const rspackConfig = modern ? rspackConfigs.modern : rspackConfigs.legacy 9 | console.log(util.inspect(rspackConfig, { depth: null, colors: true })) 10 | } 11 | -------------------------------------------------------------------------------- /examples/serve-with-fastify/server.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify') 2 | const jetpack = require('../../serve') 3 | 4 | const app = fastify({ logger: true }) 5 | 6 | app.get('/api/data', (req, res) => { 7 | res.send('hello') 8 | }) 9 | 10 | app.get('/*', (req, res) => { 11 | jetpack(req.raw, res.raw).then(() => { 12 | res.hijack() 13 | }) 14 | }) 15 | 16 | app.listen({ port: 3000 }, function () { 17 | console.log('Running server on http://localhost:3000') 18 | }) 19 | -------------------------------------------------------------------------------- /test/fixtures/pkg-with-lightningcss/styles.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | flex: 1; 3 | } 4 | .bar { 5 | flex: 1 1; 6 | } 7 | .foz { 8 | flex: 1 1 0; 9 | } 10 | .baz { 11 | flex: 1 1 0px; 12 | } 13 | 14 | @custom-media --wide-window (min-width: 30em); 15 | 16 | @media (--wide-window) { 17 | background: yellow; 18 | } 19 | 20 | .logo { 21 | backdrop-filter: blur(10px); 22 | background: yellow; 23 | } 24 | 25 | .button { 26 | -webkit-transition: background 200ms; 27 | -moz-transition: background 200ms; 28 | transition: background 200ms; 29 | } 30 | -------------------------------------------------------------------------------- /test/helpers/rspackConfigForTests.js: -------------------------------------------------------------------------------- 1 | module.exports = (config) => { 2 | /** 3 | * Webpack outputs the © symbol differently on a Mac and on Travis CI 4 | * which means the ava snapshots don't match up. This replaces the copyright 5 | * symbol with something else in tests, so that the builds are identical in 6 | * all environments. 7 | */ 8 | config.module.rules.unshift({ 9 | test: /core-js\/internals\/shared\.js$/, 10 | loader: 'string-replace-loader', 11 | options: { 12 | search: '© 2019 Denis Pushkarev (zloirock.ru)', 13 | replace: '-' 14 | } 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /examples/lazy-loading/index.js: -------------------------------------------------------------------------------- 1 | import './styles.css' 2 | 3 | function main () { 4 | document.querySelector('#root').innerHTML = ` 5 |

HMR!

6 |

Hello world.

7 |

Paragraph two.

8 | 9 | ` 10 | 11 | document.querySelector('#load').addEventListener('click', () => { 12 | import('./more.js').then(({ more }) => { 13 | const p = Array.from(document.querySelectorAll('p')) 14 | p[p.length - 1].appendChild(more()) 15 | }) 16 | }) 17 | } 18 | 19 | main() 20 | 21 | if (module.hot) { 22 | module.hot.accept() 23 | } 24 | -------------------------------------------------------------------------------- /docs/recipe-06-server-side-rendering.md: -------------------------------------------------------------------------------- 1 | # Server Side Rendering 2 | 3 | TBD. 4 | 5 | Often, you should just reach for a solid existing framework such [Next.js](https://nextjs.org/) or [Gatsby](https://www.gatsbyjs.org/). 6 | 7 | But sometimes those solutions are either too heavy or don't fit the requirements at hand. 8 | 9 | It's possible to whip up a simple custom SSR setup with jetpack, here's a rough proof of concept from a previous SSR project I worked on: 10 | 11 | ![image](https://user-images.githubusercontent.com/324440/48574126-461a3b80-e906-11e8-9d91-1354c2567b06.png) 12 | 13 | I just need to flesh it out into a working example. 14 | -------------------------------------------------------------------------------- /lib/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{{title}}} 7 | {{#each assets.css}} 8 | 9 | {{/each}} 10 | {{{head}}} 11 | 12 | 13 | {{{body}}} 14 | {{#if runtime}} 15 | 18 | {{/if}} 19 | {{#each assets.js}} 20 | 21 | {{/each}} 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/rspack.hot.js: -------------------------------------------------------------------------------- 1 | const rspack = require('@rspack/core') 2 | const ReactRefreshPlugin = require('@rspack/plugin-react-refresh') 3 | 4 | module.exports = (config, options) => { 5 | if (!options.production && options.hot) { 6 | config.plugins.push(new rspack.HotModuleReplacementPlugin()) 7 | 8 | Object.keys(config.entry).forEach((e) => { 9 | config.entry[e] = [ 10 | require.resolve('webpack-hot-middleware/client') + '?path=/assets/__webpack_hmr&reload=false' 11 | ].concat(config.entry[e]) 12 | }) 13 | 14 | if (options.react) { 15 | config.plugins.push(new ReactRefreshPlugin()) 16 | config.resolve.alias['react-refresh/runtime'] = require.resolve('react-refresh/runtime') 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/rspack.ts.js: -------------------------------------------------------------------------------- 1 | const browsers = require('./browsers') 2 | 3 | module.exports = (config, options) => { 4 | config.module.rules[0].oneOf.push({ 5 | test: /\.(ts|tsx)$/, 6 | exclude: /(node_modules)/, 7 | use: [ 8 | { 9 | loader: 'builtin:swc-loader', 10 | options: { 11 | env: { 12 | targets: browsers.query(options), 13 | coreJs: '3.40', 14 | mode: 'usage' 15 | }, 16 | jsc: { 17 | parser: { 18 | syntax: 'typescript', 19 | exportDefaultFrom: true, 20 | jsx: true 21 | }, 22 | externalHelpers: true, 23 | transform: {} 24 | }, 25 | isModule: 'unknown' 26 | } 27 | } 28 | ] 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /examples/with-react/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import './App.scss' 3 | 4 | function HooksCounter () { 5 | const [count, setCount] = useState(0) 6 | const incrementCount = () => setCount(currentCount => currentCount + 1) 7 | return 8 | } 9 | 10 | class ClassCounter extends React.Component { 11 | constructor () { 12 | super(); 13 | this.state = { 14 | count: 0 15 | } 16 | } 17 | render () { 18 | const incrementCount = () => this.setState({ count: this.state.count + 1 }); 19 | return 20 | } 21 | } 22 | 23 | function App () { 24 | return <> 25 | Component with hooks: 26 | 27 |
28 |
29 | Class component: 30 | 31 | 32 | 33 | } 34 | 35 | export default App 36 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const neostandard = require('neostandard') 2 | 3 | // Function to patch the neostandard config 4 | function superneostandard(neoConfig) { 5 | const config = neostandard(neoConfig) 6 | 7 | // allow jsx in js files! that's all we're doing! 8 | const jsxIndex = config.findIndex((c) => c && c.name === 'neostandard/jsx') 9 | const jsxCfg = config[jsxIndex] 10 | config[jsxIndex] = { 11 | ...jsxCfg, 12 | name: 'neostandard/jsx', 13 | files: ['**/*.{js,jsx,ts,tsx}'], 14 | ignores: [], // remove ignore patterns that excluded .js 15 | languageOptions: { 16 | ...(jsxCfg.languageOptions || {}), 17 | parserOptions: { 18 | ...((jsxCfg.languageOptions && jsxCfg.languageOptions.parserOptions) || {}), 19 | ecmaFeatures: { jsx: true } 20 | } 21 | } 22 | } 23 | 24 | return config 25 | } 26 | 27 | // Build + patch neostandard config 28 | module.exports = superneostandard({ 29 | noStyle: true, 30 | ignores: ['dist/**/*', ...neostandard.resolveIgnoresFromGitignore()] 31 | }) 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: ['master'] 9 | pull_request: 10 | branches: ['master'] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [20.x, 22.x] 22 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: 'npm' 31 | - run: npm ci 32 | - run: npm run build --if-present 33 | - run: npm test 34 | -------------------------------------------------------------------------------- /lib/proxy.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const querystring = require('querystring') 3 | 4 | module.exports = (target, log) => (req, res) => { 5 | if (req.params[0] && target.includes('/:splat')) { 6 | target = target.replace('/:splat', req.params[0]) 7 | } 8 | 9 | const parsed = new URL(target) 10 | const path = Object.keys(req.query).length > 0 ? req.path + '?' + querystring.stringify(req.query) : req.path 11 | const reqOpt = { 12 | host: parsed.hostname, 13 | port: parsed.port, 14 | headers: req.headers, 15 | method: req.method, 16 | path, 17 | params: req.params, 18 | session: req.session 19 | } 20 | const proxyReq = http.request(reqOpt, function (proxyRes) { 21 | proxyRes.pipe(res) 22 | res.status(proxyRes.statusCode) 23 | Object.keys(proxyRes.headers).forEach((header) => res.set(header, proxyRes.headers[header])) 24 | }) 25 | req.pipe(proxyReq) 26 | 27 | proxyReq.on('error', function (err) { 28 | log.error(`Failed to proxy ${req.url} to ${target}:`, err.message) 29 | res.status(502) 30 | res.send({ error: err }) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /docs/03-customizing-swc.md: -------------------------------------------------------------------------------- 1 | # Customizing SWC 2 | 3 | Jetpack uses SWC via swc-loader by default, because: 4 | 5 | - this way you get JSX support out of the box 6 | - you can be sure that the code you write will run in all the browsers you support 7 | 8 | Jetpack uses the following SWC options: 9 | 10 | ```js 11 | { 12 | env: { 13 | targets: browsers.query(options), 14 | coreJs: 3, 15 | mode: 'entry', 16 | exclude: ['transform-typeof-symbol'] 17 | }, 18 | jsc: { 19 | parser: { 20 | jsx: true, 21 | exportDefaultFrom: true 22 | }, 23 | transform: {}, 24 | } 25 | } 26 | ``` 27 | 28 | See [lib/webpack.js.js](../lib/webpack.js.js) for the exact configuration. 29 | 30 | These options can be modified via `jetpack.config.js`: 31 | 32 | ``` 33 | module.exports = { 34 | webpack: (config, options) => { 35 | for (const rule of config.module.rules[0].oneOf) { 36 | if (rule.use) { 37 | for (const loader of rule.use) { 38 | if (loader.loader.includes('/swc-loader')) { 39 | loader.options.env.mode = 'usage' 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/02-customizing-rspack.md: -------------------------------------------------------------------------------- 1 | # Customizing Rspack 2 | 3 | You can extend the default rspack config using `jetpack.config.js`. 4 | 5 | Here's an example of using an extra loader and a couple plugins. 6 | 7 | ```js 8 | // jetpack exposes it's own copy of rspack so that you can use rspack plugins 9 | const rspack = require('jetpack/rspack') 10 | const WorkboxWebpackPlugin = require('workbox-webpack-plugin') 11 | 12 | module.exports = { 13 | rspack: (config, options) => { 14 | // unshift to run before other loaders, since 15 | // we're overriding the preconfigured svg loader 16 | config.module.rules[0].oneOf.unshift({ 17 | test: /\.svg$/, 18 | use: ['@svgr/webpack'] 19 | }) 20 | 21 | // reference jetpack's rspack to use the 22 | // plugins that ship with rspack 23 | config.plugins.push( 24 | new rspack.DefinePlugin() 25 | ) 26 | 27 | // in production, add the lovely Workbox plugin 28 | if (options.production) { 29 | config.plugins.push( 30 | new WorkboxWebpackPlugin.GenerateSW({ 31 | clientsClaim: true, 32 | exclude: [/\.map$/, /asset-manifest\.json$/] 33 | }) 34 | ) 35 | } 36 | 37 | return config 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /lib/inspect.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs/promises') 2 | const path = require('path') 3 | const rspack = require('@rspack/core') 4 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') 5 | const createRspackConfig = require('./rspack.config') 6 | 7 | module.exports = async function (options, log) { 8 | log.info('Generating report...') 9 | process.env.NODE_ENV = 'production' 10 | const rspackConfigs = createRspackConfig(options, log) 11 | const rspackConfig = !options.target.modern ? rspackConfigs.legacy : rspackConfigs.modern 12 | rspackConfig.plugins = rspackConfig.plugins || [] 13 | rspackConfig.plugins.push(new BundleAnalyzerPlugin()) 14 | const compiler = rspack(rspackConfig) 15 | compiler.run(async function (err, stats) { 16 | if (err) return console.log(err) 17 | if (options.static && (await isDir(path.join(options.dir, options.static)))) { 18 | await fs.cp(path.join(options.dir, options.static), path.join(options.dir, options.dist, options.static), { 19 | recursive: true 20 | }) 21 | } 22 | console.log( 23 | stats.toString({ 24 | colors: true 25 | }) 26 | ) 27 | }) 28 | } 29 | 30 | async function isDir(path) { 31 | try { 32 | const stats = await fs.stat(path) 33 | return stats.isDirectory() 34 | } catch (err) { 35 | return false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('picocolors') 2 | 3 | let lastType 4 | 5 | function append(...args) { 6 | if (lastType === 'status') { 7 | if (process.stdout.isTTY) { 8 | process.stdout.clearLine(0) 9 | process.stdout.cursorTo(0) 10 | } 11 | } 12 | console.log(...args) 13 | } 14 | 15 | function replace(...args) { 16 | if (process.stdout.isTTY) { 17 | process.stdout.clearLine(0) 18 | process.stdout.cursorTo(0) 19 | } 20 | let line = args.join(' ') 21 | if (process.stdout.isTTY) { 22 | const columns = process.stdout.columns || 89 23 | line = line.substr(0, columns) 24 | } 25 | process.stdout.write(line) 26 | if (!process.stdout.isTTY) { 27 | process.stdout.write('\n') 28 | } 29 | } 30 | 31 | module.exports = function logger(logLevels, prefix = 'jetpack ›') { 32 | return { info, warn, error, status } 33 | 34 | function info(...args) { 35 | if (!logLevels.info) return 36 | append(chalk.green(prefix), ...args) 37 | lastType = 'info' 38 | } 39 | 40 | function warn(...args) { 41 | if (!logLevels.info) return 42 | append(chalk.yellow(prefix), ...args) 43 | lastType = 'warn' 44 | } 45 | 46 | function error(...args) { 47 | if (!logLevels.info) return 48 | append(chalk.red(prefix), ...args) 49 | lastType = 'error' 50 | } 51 | 52 | function status(...args) { 53 | if (!logLevels.progress) return 54 | replace(chalk.green(prefix), ...args.map(chalk.gray)) 55 | lastType = 'status' 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/clean.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs/promises') 2 | const path = require('path') 3 | const readline = require('readline') 4 | const pc = require('picocolors') 5 | 6 | function confirm({ question, default: defaultValue = false }) { 7 | return new Promise((resolve, reject) => { 8 | const rl = readline.createInterface({ 9 | input: process.stdin, 10 | output: process.stdout 11 | }) 12 | 13 | const defaultHint = defaultValue ? '(Y/n)' : '(y/N)' 14 | const prompt = `${pc.green('?')} ${pc.gray(question)} ${pc.gray(defaultHint)} ` 15 | 16 | rl.question(prompt, (answer) => { 17 | rl.close() 18 | 19 | const normalized = answer.trim().toLowerCase() 20 | 21 | // If empty, use default value 22 | if (normalized === '') { 23 | if (defaultValue) { 24 | resolve() 25 | } else { 26 | reject(new Error('No input provided')) 27 | } 28 | return 29 | } 30 | 31 | // Check for yes responses 32 | if (normalized === 'y' || normalized === 'yes') { 33 | resolve() 34 | } else { 35 | reject(new Error('User cancelled')) 36 | } 37 | }) 38 | }) 39 | } 40 | 41 | module.exports = async function clean(options) { 42 | const target = path.join(options.dir, options.dist) 43 | 44 | try { 45 | await confirm({ 46 | question: `Are you sure you want to remove ${target}?`, 47 | default: false 48 | }) 49 | await fs.rm(target, { recursive: true, force: true }) 50 | } catch (err) { 51 | // nothing happens 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/05-customizing-browserslist.md: -------------------------------------------------------------------------------- 1 | # Customizing browserslist 2 | 3 | Jetpack compiles your code using `swc-loader` and rspack's `builtin:lightningcss-loader` plugins to ensure the code is ready for all the browsers you support. Those projects follow the lovely [browserlist](https://github.com/browserslist/browserslist) convention. 4 | 5 | Jetpack supports differential serving, that is it can produce 2 bundles - modern and legacy. By default jetpack only builds a modern bundle using the `defaults` [browserslist query](https://browsersl.ist/#q=defaults): 6 | 7 | ``` 8 | defaults 9 | ``` 10 | 11 | Which is shortcut for: 12 | 13 | ``` 14 | > 0.5% 15 | last 2 versions 16 | Firefox ESR 17 | not dead 18 | ``` 19 | 20 | This query ensures that only modern browsers with full support for async/await are targeted. This removes the need to transpiled async/await and many other modern JavaScript features. 21 | 22 | To configure the list of browsers, you can use any of methods supported by browserslist, but make sure to use `modern` and `legacy` environments: 23 | 24 | #### package.json 25 | 26 | ```js 27 | { 28 | "private": true, 29 | "dependencies": { 30 | "autoprefixer": "^6.5.4" 31 | }, 32 | "browserslist": { 33 | "modern": [ 34 | "last 1 version", 35 | "> 1%", 36 | "IE 10" 37 | ] 38 | } 39 | } 40 | ``` 41 | 42 | #### .browserslistrc config file 43 | 44 | ``` 45 | # Browsers that we support 46 | [modern] 47 | last 1 version 48 | > 1% 49 | IE 10 # sorry 50 | 51 | [legacy] 52 | > 0.1% 53 | ``` 54 | 55 | See [browserslist docs](https://github.com/browserslist/browserslist) for more details. 56 | -------------------------------------------------------------------------------- /docs/08-hot-reloading.md: -------------------------------------------------------------------------------- 1 | # Hot reloading 2 | 3 | Hot reloading is turned on by default in jetpack when in development mode. You can turn it off by passing `-r` arg or setting `hot: false` in the `jetpack.config.js` file. 4 | 5 | ## CSS 6 | 7 | CSS is hot reloaded automatically with no extra steps. 8 | 9 | ## React 10 | 11 | React components are hot reloaded automatically using `fast-refresh` (via the [@rspack/plugin-react-refresh](https://github.com/rspack-contrib/rspack-plugin-react-refresh) plugin). 12 | 13 | ## Vanilla JS 14 | 15 | If you're not using React, hot reloading can still be used. That's something that rspack supports natively. All you need to do is add the following bit of code somewhere in your application, preferably in the entry module. 16 | 17 | ```js 18 | if (module.hot) { 19 | module.hot.accept() 20 | module.hot.dispose(() => { 21 | // perform cleanup 22 | }) 23 | } 24 | ``` 25 | 26 | Now rspack will re execute the changed code. If you built your application in a way where it can be unrendered in dispose() and rendered again, you'll get a nice hot reloading behaviour. Note, you'll want to store the state on window or in localStorage if you want the app to remain in the same state after rerendering. 27 | 28 | If `module.hot.accept` is not called by your code, you'll only get hot reloading behaviour for your css and will have to manually refresh the page for any other changes. 29 | 30 | ## Gotchas 31 | 32 | It's common to get the following error in the network panel: 33 | 34 | ``` 35 | GET http://localhost:3030/assets/__webpack_hmr net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK) 36 | ``` 37 | 38 | This could happen if you keep your client and server code in the same project and use `nodemon` to restart server on code changes. Nodemon restarts the server and the hot reload gets interrupted. Make sure to ignore changes to client code in this case, e.g. `nodemon -i app/client .`. 39 | -------------------------------------------------------------------------------- /lib/rspack.react.js: -------------------------------------------------------------------------------- 1 | const { existsSync, readFileSync } = require('fs') 2 | const path = require('path') 3 | 4 | module.exports = (config, options) => { 5 | if (options.react) { 6 | const reactPath = getPkgPath('react', options.dir) 7 | if (reactPath) { 8 | config.resolve.alias.react = reactPath 9 | } 10 | const reactDOMPath = getPkgPath('react-dom', options.dir) 11 | if (reactDOMPath) { 12 | config.resolve.alias['react-dom'] = reactDOMPath 13 | } 14 | 15 | config.module.rules[0].oneOf.forEach((rule) => { 16 | if (rule.use) { 17 | rule.use.forEach((loader) => { 18 | if (loader.loader === 'builtin:swc-loader') { 19 | loader.options.jsc.transform.react = { 20 | runtime: 'automatic', 21 | development: !options.production, 22 | refresh: !options.production && options.hot 23 | } 24 | } 25 | }) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | function getPkgPath(pkg, dir, opts = {}) { 32 | try { 33 | const entry = require.resolve(pkg, { paths: [dir] }) 34 | 35 | let curr = path.dirname(entry) 36 | const root = path.parse(curr).root 37 | 38 | while (true) { 39 | const pkgJsonPath = path.join(curr, 'package.json') 40 | if (existsSync(pkgJsonPath)) { 41 | try { 42 | const json = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) 43 | if (json.name === pkg) return curr 44 | } catch { 45 | // if unreadable/invalid JSON, keep walking 46 | } 47 | } 48 | if (curr === root) break 49 | const parent = path.dirname(curr) 50 | if (parent === curr) break 51 | curr = parent 52 | } 53 | 54 | return null // Shouldn't usually happen if resolve succeeded 55 | } catch (err) { 56 | if (err && err.code === 'MODULE_NOT_FOUND') return null 57 | throw err 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/browsers.js: -------------------------------------------------------------------------------- 1 | const chalk = require('picocolors') 2 | const browserslist = require('browserslist') 3 | 4 | function browsers(options) { 5 | if (options.target.modern) { 6 | const modern = query({ modern: true }) 7 | const modernBrowsers = browserslist(modern) 8 | console.log(chalk.yellow('[modern query]')) 9 | console.log((Array.isArray(modern) ? modern : (modern || '').split(', ')).join('\n')) 10 | console.log('') 11 | console.log(chalk.yellow('[modern browsers]')) 12 | console.log(modernBrowsers.join('\n')) 13 | console.log('') 14 | console.log(chalk.yellow(`[modern coverage ${options.coverage || 'globally'}]`)) 15 | console.log(browserslist.coverage(modernBrowsers, options.coverage || undefined).toFixed(2) + '%') 16 | console.log('') 17 | } 18 | 19 | if (options.target.legacy) { 20 | const legacy = query({ modern: false }) || browserslist.defaults 21 | const legacyBrowsers = browserslist(legacy) 22 | console.log(chalk.yellow('[legacy query]')) 23 | console.log((Array.isArray(legacy) ? legacy : (legacy || '').split(', ')).join('\n')) 24 | console.log('') 25 | console.log(chalk.yellow('[legacy browsers]')) 26 | console.log(legacyBrowsers.join('\n')) 27 | console.log('') 28 | console.log(chalk.yellow(`[legacy coverage ${options.coverage || 'globally'}]`)) 29 | console.log(browserslist.coverage(legacyBrowsers, options.coverage || undefined).toFixed(2) + '%') 30 | console.log('') 31 | } 32 | } 33 | 34 | function query(options) { 35 | const browserslistEnv = options.modern ? 'modern' : 'legacy' 36 | const browsers = browserslist.loadConfig({ env: browserslistEnv, path: '.' }) 37 | return browsers 38 | } 39 | 40 | async function regex(options) { 41 | const { getUserAgentRegex } = await import('browserslist-useragent-regexp') 42 | return getUserAgentRegex({ 43 | browsers: query(options), 44 | allowHigherVersions: true 45 | }) 46 | } 47 | 48 | module.exports = browsers 49 | module.exports.query = query 50 | module.exports.regex = regex 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jetpack", 3 | "version": "4.3.0", 4 | "main": "index.js", 5 | "description": "Jetpack wraps webpack and nodemon to give you the best development workflow.", 6 | "license": "MIT", 7 | "author": "Karolis Narkevicius", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/KidkArolis/jetpack.git" 11 | }, 12 | "keywords": [ 13 | "webpack", 14 | "jetpack", 15 | "build" 16 | ], 17 | "engines": { 18 | "node": ">=20" 19 | }, 20 | "bin": { 21 | "jetpack": "./bin/jetpack" 22 | }, 23 | "scripts": { 24 | "test": "ava && eslint . && prettier --check '**/*.{js,json,css,yml}'", 25 | "format": "prettier --write '**/*.{js,json,css,yml}'", 26 | "release": "np", 27 | "release-alpha": "np --tag=alpha --any-branch --no-cleanup" 28 | }, 29 | "dependencies": { 30 | "@rspack/core": "^1.6.4", 31 | "@rspack/plugin-react-refresh": "^1.5.3", 32 | "@swc/core": "^1.15.3", 33 | "@swc/helpers": "^0.5.17", 34 | "browserslist": "^4.28.0", 35 | "browserslist-useragent-regexp": "^4.1.3", 36 | "core-js": "^3.47.0", 37 | "css-loader": "^7.1.2", 38 | "express": "^5.1.0", 39 | "handlebars": "^4.7.8", 40 | "parseurl": "^1.3.3", 41 | "picocolors": "^1.1.1", 42 | "prepend-transform": "0.0.1019", 43 | "react-refresh": "^0.18.0", 44 | "regenerator-runtime": "^0.14.1", 45 | "sass-embedded": "^1.93.3", 46 | "sass-loader": "^16.0.6", 47 | "sass-resources-loader": "^2.2.5", 48 | "send": "^1.2.0", 49 | "style-loader": "^4.0.0", 50 | "webpack-bundle-analyzer": "^5.0.1", 51 | "webpack-dev-middleware": "^7.4.5", 52 | "webpack-format-messages": "^3.0.1", 53 | "webpack-hot-middleware": "^2.26.1" 54 | }, 55 | "devDependencies": { 56 | "ava": "^6.4.1", 57 | "eslint": "^9.39.1", 58 | "execa": "^9.6.0", 59 | "klaw": "^4.1.0", 60 | "neostandard": "^0.12.2", 61 | "prettier": "^3.6.2", 62 | "string-replace-loader": "^3.2.0" 63 | }, 64 | "ava": { 65 | "files": [ 66 | "test/**/*.test.js" 67 | ], 68 | "timeout": "1m" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/retryChunkLoadPlugin.js: -------------------------------------------------------------------------------- 1 | const { RuntimeGlobals, RuntimeModule } = require('@rspack/core') 2 | 3 | const name = 'RetryChunkLoadPlugin' 4 | 5 | class RetryChunkLoadPlugin { 6 | #maxAttempts 7 | #base 8 | #multiplier 9 | 10 | constructor({ maxAttempts = 5, base = 1.8, multiplier = 500 } = {}) { 11 | if (typeof maxAttempts !== 'number' || maxAttempts < 1) { 12 | throw new Error('Invalid `maxAttempts`') 13 | } 14 | 15 | if (typeof base !== 'number' || base < 1) { 16 | throw new Error('Invalid `base`') 17 | } 18 | 19 | if (typeof multiplier !== 'number' || multiplier < 0) { 20 | throw new Error('Invalid `multiplier`') 21 | } 22 | 23 | this.#maxAttempts = maxAttempts 24 | this.#base = base 25 | this.#multiplier = multiplier 26 | } 27 | 28 | apply(compiler) { 29 | compiler.hooks.thisCompilation.tap(name, (compilation) => { 30 | compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.ensureChunk).tap(name, (chunk, set) => { 31 | set.add(RuntimeGlobals.ensureChunk) 32 | const script = ` 33 | var initialEnsureChunk = ${RuntimeGlobals.ensureChunk}; 34 | 35 | ${RuntimeGlobals.ensureChunk} = function (chunkId) { 36 | var attemptCount = 0; 37 | 38 | return new Promise(function (resolve, reject) { 39 | var load = function () { 40 | initialEnsureChunk(chunkId).then((res) => resolve(res)).catch((e) => { 41 | if (++attemptCount >= ${this.#maxAttempts}) { 42 | reject(e); 43 | } else { 44 | setTimeout(() => load(), (${this.#base} ** (attemptCount - 1)) * ${this.#multiplier}) 45 | } 46 | }); 47 | }; 48 | 49 | load(); 50 | }); 51 | }; 52 | ` 53 | 54 | class CustomRuntimeModule extends RuntimeModule { 55 | constructor() { 56 | super(name, RuntimeModule.STAGE_BASIC) 57 | } 58 | 59 | generate() { 60 | return script 61 | } 62 | } 63 | 64 | compilation.addRuntimeModule(chunk, new CustomRuntimeModule()) 65 | }) 66 | }) 67 | } 68 | } 69 | 70 | module.exports = RetryChunkLoadPlugin 71 | -------------------------------------------------------------------------------- /docs/09-comparison.md: -------------------------------------------------------------------------------- 1 | ## Comparison 2 | 3 | There exist many tools that try and make working with webpack/rspack easier: 4 | 5 | * **create-react-app** – a great and powerful tool. I personally tend not to use, because I find it slightly big and intimidating with a large codebase, it's overly React specific and brings in opinionated tools such as `jest` testing framework. But it's a very comprehensive tool with great documentation, so keep using it if it works for you! 6 | * **pwa-cli** – very similar to what jetpack is trying to do. Has a neat plugin system for extending its functionality beyond what the core provides. Why jetpack and not pwa-cli? Not sure, you'll have to try both and see what you prefer. 7 | 8 | There are also alternatives to webpack/rspack: 9 | 10 | * **Parcel** is making 🌊. Let's see what version 2.0 brings to the table. It has very similar goals to that of jetpack's – most things should just work and be convenient without configuration. 11 | * **Browserify** – probably still has a hard core fan base ;) with dev servers similar to jetpack, e.g. [budo](https://github.com/mattdesl/budo) and [bankai](https://github.com/choojs/bankai) to mention a couple. 12 | 13 | There also exist higher level frameworks for certain use cases: 14 | 15 | * **Next.js** - use this when you need server side rendering. Server side rendering can be tricky to wrap your head around and so when you just want an Single Page Application – jetpack might be a better choice. 16 | * **Gatsby.js** - use this when you're building a web app that can be exported to a bunch of html files – it's a great fit for websites. For richer, interactive web applications, jetpack might just be a simpler choice. 17 | 18 | #### Conclusion 19 | 20 | In general, where jetpack tries to be different is: 21 | 22 | - make it possible to run globally anywhere on your machine, just like you can run `node ~/Desktop/test.js` 23 | - have [good workflow](./06-workflow-and-deployment.md) for simultaneously developing the client and the server 24 | - make sure that if no configuration is touched, the application is bundled in the best possible way for production 25 | - serve the SPA use case well (as opposed to SSR or SSG) 26 | 27 | ## Bonus 28 | 29 | Because jetpack avoids anything too fancy, a lot of the time your app might already work with jetpack out of the box. For example, this works: 30 | 31 | $ npx create-react-app my-app 32 | $ cd my-app 33 | $ jetpack 34 | -------------------------------------------------------------------------------- /lib/rspack.css.js: -------------------------------------------------------------------------------- 1 | const browsers = require('./browsers') 2 | 3 | module.exports = async (config, options) => { 4 | config.module.rules[0].oneOf.push({ 5 | test: /\.css$/, 6 | exclude: [/\.global\.css$/, /node_modules/], 7 | use: [ 8 | { 9 | loader: options.production 10 | ? require('@rspack/core').CssExtractRspackPlugin.loader 11 | : require.resolve('style-loader') 12 | }, 13 | { 14 | loader: require.resolve('css-loader'), 15 | options: { 16 | importLoaders: 1, 17 | sourceMap: false, 18 | ...(options.css.modules && { 19 | modules: { 20 | localIdentName: 21 | options.mode === 'production' 22 | ? '[name]--[local]___[hash:base64:5]' 23 | : '[path][name]--[local]___[hash:base64:5]' 24 | } 25 | }) 26 | } 27 | }, 28 | { 29 | loader: 'builtin:lightningcss-loader', 30 | options: { 31 | sourceMap: !!options.sourceMaps, 32 | targets: browsers.query(options), 33 | include: options.css?.features?.include, 34 | exclude: options.css?.features?.exclude 35 | } 36 | } 37 | ] 38 | }) 39 | 40 | config.module.rules[0].oneOf.push({ 41 | test: /\.css$/, 42 | use: [ 43 | { 44 | loader: options.production 45 | ? require('@rspack/core').CssExtractRspackPlugin.loader 46 | : require.resolve('style-loader') 47 | }, 48 | { 49 | loader: require.resolve('css-loader'), 50 | options: { 51 | importLoaders: 1, 52 | sourceMap: false 53 | } 54 | }, 55 | { 56 | loader: 'builtin:lightningcss-loader', 57 | options: { 58 | sourceMap: !!options.sourceMaps, 59 | targets: browsers.query(options), 60 | include: options.css?.features?.include, 61 | exclude: options.css?.features?.exclude 62 | } 63 | } 64 | ] 65 | }) 66 | 67 | if (options.production) { 68 | const { CssExtractRspackPlugin } = require('@rspack/core') 69 | config.plugins.push( 70 | new CssExtractRspackPlugin({ 71 | filename: '[name].[contenthash:8].css', 72 | chunkFilename: '[name].[contenthash:8].chunk.css', 73 | // if css modules are used in the project, then 74 | // the order should not matter (unless :global() is used) 75 | ignoreOrder: options.css.modules 76 | }) 77 | ) 78 | if (options.minify) { 79 | const { LightningCssMinimizerRspackPlugin } = require('@rspack/core') 80 | config.optimization.minimizer.push(new LightningCssMinimizerRspackPlugin()) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/rspack.js.js: -------------------------------------------------------------------------------- 1 | const browsers = require('./browsers') 2 | 3 | module.exports = (config, options) => { 4 | config.module.rules[0].oneOf.push({ 5 | test: /\.(js|mjs|jsx)$/, 6 | exclude: /(node_modules)/, 7 | use: [ 8 | { 9 | loader: 'builtin:swc-loader', 10 | options: { 11 | env: { 12 | targets: browsers.query(options), 13 | coreJs: '3.40', 14 | mode: 'usage' 15 | }, 16 | jsc: { 17 | parser: { 18 | syntax: 'ecmascript', 19 | exportDefaultFrom: true, 20 | jsx: true 21 | }, 22 | externalHelpers: true, 23 | transform: {} 24 | }, 25 | isModule: 'unknown' 26 | } 27 | } 28 | ] 29 | }) 30 | 31 | // transpile node_modules 32 | config.module.rules[0].oneOf.push({ 33 | test: /\.(js|mjs)$/, 34 | include(filepath) { 35 | if (filepath.startsWith(config.resolve.alias['core-js'])) return false 36 | if (filepath.startsWith(config.resolve.alias['regenerator-runtime'])) return false 37 | if (filepath.startsWith(config.resolve.alias['@swc/helpers'])) return false 38 | if (filepath.includes('node_modules/ansi-html-community/')) return false 39 | if (filepath.includes('node_modules/ansi-regex/')) return false 40 | if (filepath.includes('node_modules/core-js/')) return false 41 | if (filepath.includes('node_modules/css-loader/')) return false 42 | if (filepath.includes('node_modules/html-entities/')) return false 43 | if (filepath.includes('node_modules/strip-ansi/')) return false 44 | if (filepath.includes('node_modules/style-loader/')) return false 45 | if (filepath.includes('node_modules/webpack-hot-middleware/')) return false 46 | 47 | return true 48 | }, 49 | use: [ 50 | { 51 | loader: 'builtin:swc-loader', 52 | options: { 53 | env: { 54 | targets: browsers.query(options), 55 | coreJs: '3.40', 56 | mode: 'usage' 57 | }, 58 | jsc: { 59 | parser: { 60 | syntax: 'ecmascript', 61 | exportDefaultFrom: true, 62 | jsx: true 63 | }, 64 | externalHelpers: true, 65 | transform: {} 66 | }, 67 | isModule: 'unknown' 68 | } 69 | } 70 | ] 71 | }) 72 | 73 | if (options.production) { 74 | config.optimization.minimizer = options.minify 75 | ? [ 76 | new (require('@rspack/core').SwcJsMinimizerRspackPlugin)({ 77 | minimizerOptions: { 78 | mangle: true, 79 | compress: true 80 | } 81 | }) 82 | ] 83 | : [] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/rspack.scss.js: -------------------------------------------------------------------------------- 1 | const browsers = require('./browsers') 2 | 3 | module.exports = async (config, options) => { 4 | config.module.rules[0].oneOf.push({ 5 | test: /\.scss$/, 6 | exclude: [/\.global\.scss$/, /node_modules/], 7 | use: [ 8 | { 9 | loader: options.production 10 | ? require('@rspack/core').CssExtractRspackPlugin.loader 11 | : require.resolve('style-loader') 12 | }, 13 | { 14 | loader: require.resolve('css-loader'), 15 | options: { 16 | importLoaders: options.css.resources ? 3 : 2, 17 | sourceMap: false, 18 | ...(options.css.modules && { 19 | modules: { 20 | localIdentName: 21 | options.mode === 'production' 22 | ? '[name]--[local]___[hash:base64:5]' 23 | : '[path][name]--[local]___[hash:base64:5]' 24 | } 25 | }) 26 | } 27 | }, 28 | { 29 | loader: 'builtin:lightningcss-loader', 30 | options: { 31 | sourceMap: !!options.sourceMaps, 32 | targets: browsers.query(options), 33 | include: options.css?.features?.include, 34 | exclude: options.css?.features?.exclude 35 | } 36 | }, 37 | { 38 | loader: require.resolve('sass-loader'), 39 | options: { 40 | api: 'modern-compiler', 41 | implementation: require.resolve('sass-embedded'), 42 | sourceMap: !!options.sourceMaps 43 | } 44 | }, 45 | options.css.resources && { 46 | loader: require.resolve('sass-resources-loader'), 47 | options: { 48 | resources: options.css.resources, 49 | sourceMap: !!options.sourceMaps 50 | } 51 | } 52 | ].filter((x) => x) 53 | }) 54 | 55 | config.module.rules[0].oneOf.push({ 56 | test: /\.scss$/, 57 | use: [ 58 | { 59 | loader: options.production 60 | ? require('@rspack/core').CssExtractRspackPlugin.loader 61 | : require.resolve('style-loader') 62 | }, 63 | { 64 | loader: require.resolve('css-loader'), 65 | options: { 66 | importLoaders: options.css.resources ? 3 : 2, 67 | sourceMap: false 68 | } 69 | }, 70 | { 71 | loader: 'builtin:lightningcss-loader', 72 | options: { 73 | sourceMap: !!options.sourceMaps, 74 | targets: browsers.query(options), 75 | include: options.css?.features?.include, 76 | exclude: options.css?.features?.exclude 77 | } 78 | }, 79 | { 80 | loader: require.resolve('sass-loader'), 81 | options: { 82 | api: 'modern-compiler', 83 | implementation: require.resolve('sass-embedded'), 84 | sourceMap: !!options.sourceMaps 85 | } 86 | }, 87 | options.css.resources && { 88 | loader: require.resolve('sass-resources-loader'), 89 | options: { 90 | resources: options.css.resources, 91 | sourceMap: !!options.sourceMaps 92 | } 93 | } 94 | ].filter((x) => x) 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /lib/rspack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const progress = require('./progress') 3 | const RetryChunkLoadPlugin = require('./retryChunkLoadPlugin.js') 4 | 5 | const plugins = { 6 | js: require('./rspack.js'), 7 | ts: require('./rspack.ts'), 8 | react: require('./rspack.react'), 9 | hot: require('./rspack.hot'), 10 | css: require('./rspack.css'), 11 | scss: require('./rspack.scss'), 12 | assets: require('./rspack.assets') 13 | } 14 | 15 | module.exports = function createRspackConfig(options, log) { 16 | return { 17 | modern: createConfig({ ...options, modern: true }, log), 18 | legacy: createConfig({ ...options, modern: false }, log) 19 | } 20 | } 21 | 22 | function createConfig(options, log) { 23 | let config = { 24 | target: 'web', 25 | entry: { 26 | bundle: options.entry 27 | }, 28 | output: { 29 | path: path.join(process.cwd(), 'assets'), 30 | filename: options.modern ? '[name].js' : '[name].legacy.js', 31 | chunkFilename: options.modern ? '[name].js' : '[name].legacy.js', 32 | publicPath: '/assets/' 33 | }, 34 | mode: 'development', 35 | devtool: options.sourceMaps, 36 | module: { 37 | rules: [ 38 | { 39 | oneOf: [] 40 | } 41 | ] 42 | }, 43 | resolve: { 44 | alias: { 45 | 'core-js': path.dirname(require.resolve('core-js')), 46 | 'regenerator-runtime': path.dirname(require.resolve('regenerator-runtime')), 47 | '@swc/helpers': path.dirname(require.resolve('@swc/helpers/package.json')) 48 | } 49 | }, 50 | optimization: { 51 | minimizer: [] 52 | }, 53 | performance: { 54 | // once you put React in .. it goes above default max 55 | maxAssetSize: 500_000, 56 | maxEntrypointSize: 500_000 57 | }, 58 | plugins: options.chunkLoadRetry 59 | ? [new RetryChunkLoadPlugin(options.chunkLoadRetry === true ? {} : options.chunkLoadRetry)] 60 | : [], 61 | devServer: { 62 | publicPath: '/assets/', 63 | stats: 'none' 64 | }, 65 | infrastructureLogging: { 66 | level: 'none' 67 | } 68 | } 69 | 70 | if (options.logLevels.progress && log) { 71 | config.plugins.push(progress(log)) 72 | } 73 | 74 | if (options.production) { 75 | config.mode = 'production' 76 | config.output.path = path.join(options.dir, options.dist, 'assets') 77 | config.output.filename = config.output.filename.replace('[name].', '[name].[contenthash].') 78 | config.output.chunkFilename = config.output.chunkFilename.replace('[name].', '[name].[contenthash].') 79 | config.output.publicPath = options.publicPath 80 | config.optimization.splitChunks = { chunks: 'all' } 81 | config.optimization.runtimeChunk = true 82 | config.optimization.usedExports = true 83 | } 84 | 85 | plugins.js(config, options) 86 | plugins.ts(config, options) 87 | plugins.react(config, options) 88 | plugins.hot(config, options) 89 | plugins.scss(config, options) 90 | plugins.css(config, options) 91 | plugins.assets(config, options) 92 | 93 | if (options.rspack) { 94 | config = options.rspack(config, options) || config 95 | } 96 | 97 | return config 98 | } 99 | -------------------------------------------------------------------------------- /lib/dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const chalk = require('picocolors') 3 | const rspackPkg = require('@rspack/core/package.json') 4 | const createRspackConfig = require('./rspack.config') 5 | const printConfig = require('./printConfig') 6 | const pkg = require('../package.json') 7 | 8 | module.exports = async function devServer(options, log) { 9 | // by default jetpack builds modern bundle in dev 10 | // but if --legacy is used or if modern is turned off 11 | // via settings, build the legacy bundle instead 12 | const modern = options.target.modern 13 | 14 | log.info(`Jetpack ${pkg.version} • Rspack ${rspackPkg.version}${modern ? '' : ' (legacy build)'} 🚀`) 15 | 16 | if (options.printConfig) { 17 | await printConfig({ options, modern, log }) 18 | return 19 | } 20 | 21 | if (options.entry) { 22 | await client({ options, log, modern }) 23 | } 24 | 25 | if (options.exec) { 26 | log.info(`Executing ${chalk.magenta(options.exec)} in a subprocess`) 27 | await server({ options, log }) 28 | } 29 | 30 | log.info(`Asset server http://localhost:${options.port}`) 31 | } 32 | 33 | async function client({ options, log, modern }) { 34 | const express = require('express') 35 | const rspack = require('@rspack/core') 36 | const webpackDevMiddleware = require('webpack-dev-middleware') 37 | const webpackHotMiddleware = require('webpack-hot-middleware') 38 | 39 | const app = express() 40 | const rspackConfigs = createRspackConfig(options, log) 41 | const rspackConfig = modern ? rspackConfigs.modern : rspackConfigs.legacy 42 | const compiler = rspack(rspackConfig) 43 | app.use(webpackDevMiddleware(compiler, Object.assign({}, rspackConfig.devServer))) 44 | app.use( 45 | webpackHotMiddleware(compiler, { 46 | path: '/assets/__webpack_hmr', 47 | log: false 48 | }) 49 | ) 50 | 51 | if (options.logLevels.info) { 52 | require('./reporter')(compiler, log, { dir: options.dir }) 53 | } 54 | 55 | if (options.static) { 56 | app.use('/assets', express.static(path.join(options.dir, options.static))) 57 | } 58 | 59 | if (typeof options.proxy === 'function') { 60 | options.proxy(app) 61 | } else { 62 | Object.keys(options.proxy).forEach((endpoint) => { 63 | const proxy = require('./proxy') 64 | app.all(endpoint, proxy(options.proxy[endpoint], log)) 65 | }) 66 | } 67 | 68 | app.get('/{*splat}', function (_req, res) { 69 | const handlebars = require('handlebars') 70 | const head = options.head && handlebars.compile(options.head)(options) 71 | const body = options.body && handlebars.compile(options.body)(options) 72 | const html = options.html && handlebars.compile(options.html)(Object.assign({}, options, { head, body })) 73 | res.send(html) 74 | }) 75 | 76 | return new Promise(function (resolve) { 77 | const server = app.listen(options.port, (error) => { 78 | if (error) { 79 | throw error 80 | } 81 | return resolve(server.address().port) 82 | }) 83 | }) 84 | } 85 | 86 | async function server({ options }) { 87 | const { execa } = await import('execa') 88 | const prepend = require('prepend-transform').default 89 | const p = execa(options.exec, { shell: true }) 90 | const prefix = chalk.gray('jetpack » ') 91 | p.stdout.pipe(prepend(prefix)).pipe(process.stdout) 92 | p.stderr.pipe(prepend(prefix)).pipe(process.stderr) 93 | } 94 | -------------------------------------------------------------------------------- /test/options.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const options = require('../lib/options') 5 | 6 | const dir = (...subdir) => path.join(__dirname, ...subdir) 7 | 8 | const base = (pkg, extra = {}) => 9 | Object.assign( 10 | { 11 | production: false, 12 | logLevels: { info: false, progress: false, none: false }, 13 | dir: dir('fixtures', pkg), 14 | assets: { js: ['/assets/bundle.js'], css: [], other: [], runtime: [] }, 15 | runtime: null, 16 | entry: '.', 17 | dist: 'dist', 18 | static: 'assets', 19 | target: { 20 | modern: true, 21 | legacy: false 22 | }, 23 | react: false, 24 | hot: true, 25 | port: 3030, 26 | sourceMaps: 'source-map', 27 | title: 'jetpack', 28 | body: "
", 29 | html: fs.readFileSync(path.join(__dirname, '..', 'lib', 'template.hbs')).toString(), 30 | head: null, 31 | exec: false, 32 | proxy: {}, 33 | minify: true, 34 | chunkLoadRetry: false, 35 | coverage: false, 36 | publicPath: '/assets/', 37 | css: { 38 | modules: false, 39 | features: { 40 | include: null, 41 | exclude: null 42 | } 43 | } 44 | }, 45 | extra 46 | ) 47 | 48 | test('creates options object from cli flags and jetpack.config.js', (t) => { 49 | const args = { flags: { dir: dir('fixtures', 'pkg-swoosh') } } 50 | const opts = options('dev', args) 51 | t.deepEqual(opts, base('pkg-swoosh')) 52 | }) 53 | 54 | test('accepts cli flags', (t) => { 55 | const args = { 56 | entry: 'some/path', 57 | flags: { 58 | dir: dir('fixtures', 'pkg-swoosh'), 59 | hot: false, 60 | port: 2800 61 | } 62 | } 63 | const opts = options('dev', args) 64 | t.deepEqual( 65 | opts, 66 | base('pkg-swoosh', { 67 | hot: false, 68 | entry: './some/path', 69 | port: 2800 70 | }) 71 | ) 72 | }) 73 | 74 | test('accepts individual js module as entry', (t) => { 75 | const args = { 76 | entry: './module.js', 77 | flags: { dir: dir('fixtures', 'pkg-individual') } 78 | } 79 | const opts = options('dev', args) 80 | t.deepEqual( 81 | opts, 82 | base('pkg-individual', { 83 | entry: './module.js' 84 | }) 85 | ) 86 | }) 87 | 88 | test('defaults to ./src if available', (t) => { 89 | const args = { flags: { dir: dir('fixtures', 'pkg-src') } } 90 | const opts = options('dev', args) 91 | t.deepEqual( 92 | opts, 93 | base('pkg-src', { 94 | entry: './src' 95 | }) 96 | ) 97 | }) 98 | 99 | test('creates options object from jetpack.config.js', (t) => { 100 | const args = { 101 | flags: { 102 | exec: true, 103 | log: 'info', 104 | dir: dir('fixtures', 'pkg-with-config') 105 | } 106 | } 107 | const opts = options('dev', args) 108 | t.deepEqual( 109 | opts, 110 | base('pkg-with-config', { 111 | port: 1234, 112 | logLevels: { info: true, progress: false, none: false }, 113 | title: 'testing', 114 | entry: './app/client', 115 | exec: 'node ./app/server', 116 | css: { 117 | modules: false, 118 | features: { 119 | 'nesting-rules': true 120 | } 121 | } 122 | }) 123 | ) 124 | }) 125 | -------------------------------------------------------------------------------- /lib/build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs/promises') 2 | const path = require('path') 3 | const rspack = require('@rspack/core') 4 | const handlebars = require('handlebars') 5 | const chalk = require('picocolors') 6 | const createRspackConfig = require('./rspack.config') 7 | const printConfig = require('./printConfig') 8 | const { recomputeAssets } = require('./options') 9 | 10 | module.exports = async function build(options, log) { 11 | log.info('Building for production...') 12 | 13 | if (options.printConfig) { 14 | if (options.target.modern) { 15 | await printConfig({ options, modern: true, log }) 16 | } 17 | if (options.target.legacy) { 18 | await printConfig({ options, modern: false, log }) 19 | } 20 | return 21 | } 22 | 23 | const rspackConfigs = createRspackConfig(options, log) 24 | const modernCompiler = rspack(rspackConfigs.modern) 25 | const legacyCompiler = rspack(rspackConfigs.legacy) 26 | 27 | log.info('Cleaning', chalk.gray(options.dist), 'directory') 28 | const target = path.join(options.dir, options.dist) 29 | await fs.rm(target, { recursive: true, force: true }) 30 | 31 | if (options.target.modern) { 32 | log.info('Building modern bundle') 33 | await run(modernCompiler, { modern: true }) 34 | } 35 | if (options.target.legacy) { 36 | log.info('Building legacy bundle') 37 | await run(legacyCompiler, { modern: false }) 38 | } 39 | 40 | async function run(compiler, { modern }) { 41 | if (options.logLevels.info) { 42 | require('./reporter')(compiler, log, { 43 | printAssets: true, 44 | dir: options.dir 45 | }) 46 | } 47 | 48 | try { 49 | await new Promise((resolve, reject) => { 50 | compiler.run(async function (err, stats) { 51 | try { 52 | if (err) { 53 | throw err 54 | } 55 | 56 | if (stats.hasErrors()) { 57 | throw new Error('Compilation failed') 58 | } 59 | 60 | // we've compiled assets, we therefore need to recompute options.assets 61 | options = recomputeAssets(options, stats.toJson({ assets: false, chunks: false, modules: false })) 62 | 63 | if (options.static && (await isDir(path.join(options.dir, options.static)))) { 64 | await fs.cp(path.join(options.dir, options.static), path.join(options.dir, options.dist, 'assets'), { 65 | recursive: true 66 | }) 67 | } 68 | 69 | const head = options.head && handlebars.compile(options.head)(options) 70 | const body = options.body && handlebars.compile(options.body)(options) 71 | const html = options.html && handlebars.compile(options.html)(Object.assign({}, options, { head, body })) 72 | html && (await fs.writeFile(path.join(target, modern ? 'index.html' : 'index.legacy.html'), html)) 73 | resolve() 74 | } catch (err) { 75 | reject(err) 76 | } 77 | }) 78 | }) 79 | } catch (err) { 80 | log.error(chalk.red(err.stack)) 81 | process.exit(1) 82 | } 83 | } 84 | 85 | // TODO sass-loader with sass-embedded seems to be holding up the process 86 | // remove once https://github.com/webpack-contrib/sass-loader/issues/1244 is fixed fully 87 | process.exit(0) 88 | } 89 | 90 | async function isDir(path) { 91 | try { 92 | const stats = await fs.stat(path) 93 | return stats.isDirectory() 94 | } catch (err) { 95 | return false 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | const { parseArgs } = require('node:util') 2 | const getOptions = require('./options') 3 | const createLogger = require('./logger') 4 | const pkg = require('../package.json') 5 | const rspackPkg = require('@rspack/core/package.json') 6 | 7 | module.exports = { run, options } 8 | 9 | const commands = ['dev', 'build', 'inspect', 'browsers', 'clean'] 10 | 11 | const config = { 12 | options: { 13 | // Boolean flags 14 | help: { type: 'boolean', short: 'h' }, 15 | version: { type: 'boolean', short: 'v' }, 16 | hot: { type: 'boolean' }, 17 | minify: { type: 'boolean' }, 18 | modern: { type: 'boolean', short: 'm' }, 19 | legacy: { type: 'boolean', short: 'l' }, 20 | 'print-config': { type: 'boolean', short: 'i' }, 21 | 22 | // String options 23 | port: { type: 'string', short: 'p' }, 24 | dir: { type: 'string', short: 'd' }, 25 | config: { type: 'string', short: 'c' }, 26 | exec: { type: 'string', short: 'x' }, 27 | log: { type: 'string', short: 'o', default: 'info,progress' }, 28 | coverage: { type: 'string' } 29 | }, 30 | allowPositionals: true 31 | } 32 | 33 | // Parse arguments 34 | const { values: flags, positionals } = parseArgs(config) 35 | 36 | // Handle --version 37 | if (flags.version) { 38 | console.log(`Jetpack ${pkg.version}\nRspack ${rspackPkg.version}`) 39 | process.exit(0) 40 | } 41 | 42 | // Handle --help 43 | if (flags.help) { 44 | printHelp() 45 | process.exit(0) 46 | } 47 | 48 | // Determine command (default to 'dev') 49 | let command = 'dev' 50 | if (positionals.length > 0 && commands.includes(positionals[0])) { 51 | command = positionals.shift() 52 | } 53 | 54 | // Handle negated flags 55 | if (process.argv.includes('--no-hot')) { 56 | flags.hot = false 57 | } 58 | if (process.argv.includes('--no-minify')) { 59 | flags.minify = false 60 | } 61 | 62 | // handle dashed flags 63 | if (flags['print-config']) { 64 | flags.printConfig = flags['print-config'] 65 | } 66 | 67 | // Add path from positionals if present 68 | let entry 69 | if (positionals.length > 0) { 70 | entry = positionals[0] 71 | } 72 | 73 | function printHelp() { 74 | console.log(` 75 | Usage: jetpack [command] [options] [path] 76 | 77 | Commands: 78 | dev run the dev server (default) 79 | build build for production 80 | inspect analyze bundle 81 | browsers print supported browsers 82 | clean remove the dist dir 83 | 84 | Options: 85 | -p, --port port, defaults to 3030 86 | -d, --dir [path] run jetpack in the context of this directory 87 | -c, --config [path] config file to use, defaults to jetpack.config.js 88 | -r, --no-hot disable hot reloading 89 | -u, --no-minify disable minification 90 | -m, --modern build a modern bundle 91 | -l, --legacy build a legacy bundle 92 | -x, --exec [path] execute an additional process, e.g. an api server 93 | -i, --print-config print the rspack config object used in the current command 94 | -o, --log [levels] select log levels: info, progress, none 95 | -v, --version print the version of jetpack and rspack 96 | -h, --help display help for command 97 | 98 | Options for browsers command: 99 | --coverage [country] display coverage for specific country, e.g. --coverage=US 100 | 101 | Examples: 102 | jetpack 103 | jetpack --port 3500 --log=all ./my/app 104 | jetpack build 105 | jetpack build --print-config 106 | jetpack inspect ./my/app 107 | jetpack browsers --coverage=US 108 | jetpack --exec 109 | jetpack --exec 'nodemon ./server.js' 110 | 111 | Versions: 112 | Jetpack ${pkg.version} 113 | Rspack ${rspackPkg.version} 114 | `) 115 | } 116 | 117 | function options() { 118 | return getOptions(command, { entry, flags, positionals }) 119 | } 120 | 121 | function run() { 122 | const opts = options() 123 | const log = createLogger(opts.logLevels) 124 | process.chdir(opts.dir) 125 | require(`./${command}`)(opts, log) 126 | } 127 | -------------------------------------------------------------------------------- /lib/serve.js: -------------------------------------------------------------------------------- 1 | /** 2 | * handle node req/res 3 | * and respond with client side app in both dev and prd! 4 | * in dev – proxy to the jetpack dev server 5 | * in prd – serve the static assets from dist 6 | * handle all that jazz so you don't have to 7 | */ 8 | 9 | const { existsSync } = require('fs') 10 | const fs = require('fs/promises') 11 | const path = require('path') 12 | const send = require('send') 13 | const http = require('http') 14 | const parseUrl = require('parseurl') 15 | const browsers = require('./browsers') 16 | const getOptions = require('./options') 17 | 18 | const options = getOptions() 19 | 20 | const env = process.env.NODE_ENV || 'development' 21 | 22 | module.exports = 23 | env === 'development' 24 | ? createProxy(`http://localhost:${options.port}`) 25 | : createServe(path.join(options.dir, options.dist)) 26 | 27 | function createProxy(target) { 28 | return function proxy(req, res, next) { 29 | try { 30 | const parsedTarget = new URL(target) 31 | const parsedReq = parseUrl(req) 32 | const reqOpt = { 33 | host: parsedTarget.hostname, 34 | port: parsedTarget.port, 35 | headers: req.headers, 36 | method: req.method, 37 | path: parsedReq.path, 38 | params: req.params, 39 | session: req.session 40 | } 41 | const proxyReq = http.request(reqOpt, function (proxyRes) { 42 | res.writeHead(proxyRes.statusCode, proxyRes.headers) 43 | proxyRes.pipe(res) 44 | }) 45 | 46 | proxyReq.on('error', function (err) { 47 | if (err.code === 'ECONNREFUSED') { 48 | const msg = ` 49 | Failed to connect to the jetpack dev server. 50 | Make sure it's running by executing: jetpack 51 | ` 52 | console.log(msg) 53 | res.writeHead(502, { 'Content-Type': 'application/json' }) 54 | res.end(JSON.stringify({ error: msg })) 55 | } else { 56 | console.log(err) 57 | res.writeHead(502, { 'Content-Type': 'application/json' }) 58 | res.end(JSON.stringify({ error: err.stack })) 59 | } 60 | }) 61 | 62 | req.pipe(proxyReq) 63 | } catch (err) { 64 | next(err) 65 | } 66 | } 67 | } 68 | 69 | function createServe(root) { 70 | const modernBundleExists = existsSync(path.join(root, 'index.html')) 71 | const legacyBundleExists = existsSync(path.join(root, 'index.legacy.html')) 72 | 73 | function getIndex(userAgent, modernBrowserRegex) { 74 | if (!legacyBundleExists && !modernBundleExists) { 75 | return null 76 | } 77 | 78 | if (!legacyBundleExists) { 79 | return '/index.html' 80 | } 81 | 82 | if (!modernBundleExists) { 83 | return '/index.legacy.html' 84 | } 85 | 86 | if (userAgent && modernBrowserRegex.test(userAgent)) { 87 | return '/index.html' 88 | } 89 | 90 | return '/index.legacy.html' 91 | } 92 | 93 | return async function serve(req, res, next) { 94 | if (req.method !== 'GET' && req.method !== 'HEAD') return next() 95 | let pathname = parseUrl(req).pathname 96 | let index = false 97 | 98 | try { 99 | const stats = await fs.stat(path.join(root, pathname)) 100 | index = !stats.isFile() 101 | } catch (err) { 102 | if (err.code === 'ENOENT') { 103 | index = true 104 | } 105 | } 106 | 107 | if (index) { 108 | pathname = getIndex(req.headers['user-agent'], await getModernBrowserRegex()) 109 | } 110 | 111 | if (pathname) { 112 | const stream = send(req, pathname, { root }) 113 | stream.on('directory', () => next()) 114 | stream.on('error', (err) => next(err)) 115 | stream.pipe(res) 116 | } else { 117 | res.send('No bundle found') 118 | } 119 | } 120 | } 121 | 122 | let modernBrowserRegex 123 | async function getModernBrowserRegex() { 124 | if (!modernBrowserRegex) { 125 | modernBrowserRegex = browsers.regex({ modern: true }).then((re) => { 126 | modernBrowserRegex = re 127 | }) 128 | } 129 | return modernBrowserRegex 130 | } 131 | -------------------------------------------------------------------------------- /test/build.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | const path = require('path') 3 | const fs = require('fs').promises 4 | const klaw = require('klaw') 5 | const os = require('os') 6 | 7 | test('build basic', async (t) => { 8 | await build(t, 'pkg-basic') 9 | }) 10 | 11 | test('build with all the features', async (t) => { 12 | await build(t, 'pkg-with-everything') 13 | }) 14 | 15 | test('build with lightningcss syntax lowering', async (t) => { 16 | const output = await build(t, 'pkg-with-lightningcss') 17 | 18 | const base = path.join(__dirname, 'fixtures', 'pkg-with-lightningcss') 19 | const inputCssPath = path.join(base, 'styles.css') 20 | const inputCss = (await fs.readFile(inputCssPath)).toString() 21 | 22 | t.true( 23 | inputCss.includes( 24 | ` 25 | .logo { 26 | backdrop-filter: blur(10px); 27 | background: yellow; 28 | } 29 | 30 | .button { 31 | -webkit-transition: background 200ms; 32 | -moz-transition: background 200ms; 33 | transition: background 200ms; 34 | } 35 | `.trim() 36 | ) 37 | ) 38 | 39 | const outputPaths = Object.keys(output) 40 | const outputCssFile = outputPaths.find((f) => f.endsWith('.css')) 41 | const outputCss = output[outputCssFile] 42 | 43 | t.true( 44 | outputCss.includes( 45 | ` 46 | .logo { 47 | backdrop-filter: blur(10px); 48 | background: #ff0; 49 | } 50 | 51 | .button { 52 | transition: background .2s; 53 | } 54 | `.trim() 55 | ) 56 | ) 57 | }) 58 | 59 | test('build with scss', async (t) => { 60 | await build(t, 'pkg-with-scss') 61 | }) 62 | 63 | test('build with cjs modules for modern js', async (t) => { 64 | const output = await build(t, 'pkg-with-cjs') 65 | const bundle = output['/assets/bundle.js'] 66 | t.true(bundle.includes(`'test '.trim()`)) 67 | t.notThrows(() => eval(bundle)) // eslint-disable-line 68 | }) 69 | 70 | test('build with esm modules for modern js', async (t) => { 71 | const output = await build(t, 'pkg-with-esm') 72 | const bundle = output['/assets/bundle.js'] 73 | t.true(bundle.includes(`'test '.trim()`)) 74 | t.notThrows(() => eval(bundle)) // eslint-disable-line 75 | }) 76 | 77 | test('build both modern and legacy bundles', async (t) => { 78 | const output = await build(t, 'pkg-with-legacy') 79 | 80 | const bundle = output['/assets/bundle.js'] 81 | t.true(bundle.includes(`const test = async ()=>'test '.trim();`)) 82 | 83 | const legacyBundle = output['/assets/bundle.legacy.js'] 84 | t.true( 85 | legacyBundle.includes(`var test = ()=>_async_to_generator(function*() { 86 | return 'test '.trim(); 87 | })();`) 88 | ) 89 | t.true(legacyBundle.includes('// `String.prototype.trim` method')) 90 | 91 | t.true(legacyBundle.includes('`Array.prototype.toReversed` method')) 92 | 93 | t.notThrows(() => eval(bundle)) // eslint-disable-line 94 | }) 95 | 96 | async function build(t, pkg) { 97 | const base = path.join(__dirname, 'fixtures', pkg) 98 | const dist = path.join(base, 'dist') 99 | 100 | await fs.rm(dist, { recursive: true, force: true }) 101 | 102 | const { execaNode } = await import('execa') 103 | const result = await execaNode(path.join(__dirname, '..', 'bin', 'jetpack'), ['build', '--log=info', '--dir', base], { 104 | // on purpose do not run in root of jetpack to ensure we're not 105 | // accidentally using something from node_modules 106 | cwd: os.tmpdir(), 107 | env: {}, 108 | extendEnv: false, 109 | all: true 110 | }) 111 | 112 | t.snapshot( 113 | result.all 114 | .replace(/^jetpack › Built in.*$/gm, '') 115 | .split('\n') 116 | .sort() 117 | .join('\n'), 118 | `jetpack output for compiling ${pkg}` 119 | ) 120 | 121 | if (result.exitCode !== 0) { 122 | console.log('Failed to build') 123 | console.log(result.stdout) 124 | console.log(result.stderr) 125 | t.true(false) 126 | } 127 | 128 | const files = [] 129 | await new Promise((resolve, reject) => { 130 | klaw(dist) 131 | .on('readable', function () { 132 | let item 133 | while ((item = this.read())) { 134 | if (!item.stats.isDirectory()) { 135 | files.push(item.path) 136 | } 137 | } 138 | }) 139 | .on('error', (err) => reject(err)) 140 | .on('end', () => resolve()) 141 | }) 142 | 143 | const output = {} 144 | for (const file of files) { 145 | const relativeFile = file.replace(dist, '') 146 | const contents = (await fs.readFile(file)).toString() 147 | t.snapshot(contents, file.replace(path.join(__dirname, '..'), '')) 148 | output[relativeFile] = contents 149 | if (relativeFile.startsWith('/assets/bundle.') && relativeFile.endsWith('.js')) { 150 | if (relativeFile.endsWith('.legacy.js')) { 151 | output['/assets/bundle.legacy.js'] = contents 152 | } else { 153 | output['/assets/bundle.js'] = contents 154 | } 155 | } 156 | } 157 | return output 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | jetpack 3 |

4 | 5 |

Rspack made more convenient.

6 |
7 | 8 | **Jetpack wraps rspack** to create a smoother developer experience. Jetpack can be used instead of @rspack/core, @rspack/cli and @rspack/dev-server without writing any configuration. Jetpack is a thin wrapper around rspack, and can be extended with any rspack configuration. 9 | 10 | - **Sensible defaults** to handle modern JavaScript, CSS and images. 11 | - **Preconfigured swc-loader** for speedy compilation. 12 | - **Preconfigured core-js** for polyfilling missing browser features. 13 | - **Preconfigured lightningcss** for css syntax lowering. 14 | - **Modern bundles by default** with no async/await transpilation. 15 | - **Differential builds** with modern/legacy bundles served based on user agent headers. 16 | - **CSS modules** one config flag away. 17 | - **SCSS** preconfigured. 18 | - **JSX detection** preconfigured. 19 | - **Hot reloading** using `fast-refresh` for React as well as for vanilla JavaScript and CSS. 20 | - **Automatic chunk splitting** with inlined runtime and HTML generation. 21 | - **Single dependency** with hassle-free updates. 22 | 23 | **Why use jetpack?** To avoid rolling your own custom rspack config or having to paste it from old projects. Jetpack has a set of defaults that should get you off the ground quickly. And with the `proxy` config or the universal `jetpack/serve` middleware you don't have to worry about wiring up rspack's dev middleware or dev server – everything _just works_. 24 | 25 | ## Usage 26 | 27 | Install globally or locally: 28 | 29 | $ npm install -g jetpack 30 | 31 | In your project with `package.json` or `index.js`, start your app on `http://localhost:3030`: 32 | 33 | $ jetpack 34 | 35 | To build the app for production to a `dist` directory: 36 | 37 | $ jetpack build 38 | 39 | Inspect the bundle size and make up: 40 | 41 | $ jetpack inspect 42 | 43 | Print what browsers will be supported: 44 | 45 | $ jetpack browsers 46 | $ jetpack browsers --coverage=GB 47 | 48 | ## Use jetpack anywhere, anytime 49 | 50 | One of jetpack goals is to help you run any piece of JavaScript in a browser as easily as it is to run node scripts. Install jetpack globally and point it to any file on your machine. This is an alternative to jsfiddle / codepen / codesandbox style of hacking on things. 51 | 52 | $ jetpack ~/Desktop/magic.js 53 | 54 | Or any project on your machine: 55 | 56 | $ jetpack --dir ~/projects/manyverse 57 | 58 | ## Use jetpack with a server API 59 | 60 | Another goal of jetpack is to assist you in building complete, production apps. Very often in addition to developing the clientside application, you are also developing an API. Jetpack has a few features to make building such apps easier. 61 | 62 | Point your `package.json#main` to your server entry and `package.json#browser` to your client entry. 63 | 64 | Now you can run your API server together with jetpack in a single command: 65 | 66 | $ jetpack -x 67 | 68 | Alternatively, specify any command to execute: 69 | $ jetpack -x 'nodemon ./api' 70 | 71 | Use this even if your server is not written in node 72 | 73 | $ jetpack -x 'rails s' 74 | 75 | Jetpack provides an ability to proxy requests to your api by specifying `proxy` configuration in `jetpack.config.js` or mounting the dev server to your application server using the `jetpack/serve` middleware. Read more about it in [Workflow and deployment](./docs/06-workflow-and-deployment.md) docs. 76 | 77 | ## Documentation 78 | 79 | - [All configuration options](./docs/01-configuration-options.md) 80 | - [Customizing Rspack](./docs/02-customizing-rspack.md) 81 | - [Customizing SWC](./docs/03-customizing-swc.md) 82 | - [Customizing Browserslist](./docs/05-customizing-browserslist.md) 83 | - [Workflow and deployment](./docs/06-workflow-and-deployment.md) 84 | - [Differential serving](./docs/07-differential-serving.md) 85 | - [Hot reloading](./docs/08-hot-reloading.md) 86 | - [Comparison to cra, pwa-cli, parcel, etc.](./docs/09-comparison.md) 87 | - [Server side rendering](./docs/recipe-06-server-side-rendering.md) 88 | 89 | ## Motivation 90 | 91 | This project is an exploration of some ideas accumulated over a few years using webpack in a variety of projects. Webpack is a very powerful and flexible tool. It applies to a lot of use cases and that is one of the reasons it has so many configuration options. Webpack also evolved over the years but preserved backward compatibility as much as possible to support the large ecosystem built around it. 92 | 93 | Rspack - a webpack compatible Rust rewrite has since been released and offers a significant performance boost over webpack. Jetpack has been updated to use rspack under the hood for improved performance. 94 | 95 | Jetpack is an exploration of how using webpack/rspack could be made easier if the defaults, the CLI usage patterns and the configuration came with good defaults. 96 | -------------------------------------------------------------------------------- /docs/07-differential-serving.md: -------------------------------------------------------------------------------- 1 | # Differential bundling and serving 2 | 3 | Jetpack supports differential bundling and serving. That is it can produce 2 bundles instead of just one. A modern and a legacy bundle. An appropriate bundle can then be served to each browser. This ensures that modern browsers get smaller and more efficient bundles. This also helps speed up development builds by transpiling less. 4 | 5 | ## Modern by default 6 | 7 | By default, jetpack only compiles for modern browsers. This is the simplest way to use jetpack since you don't have to worry about how to do differential serving. You can use `jetpack/serve`, `express.static`, Nginx or CDN to serve your bundles. 8 | 9 | You can tweak what modern browsers are targeted if you would like to support more browser's than jetpack's default by creating a custom `.browserslistrc`, e.g.: 10 | 11 | ``` 12 | >0.5% 13 | ``` 14 | 15 | ``` 16 | # use browserlist's defaults 17 | defaults 18 | ``` 19 | 20 | ## Differential builds 21 | 22 | Instead of just the default modern bundle, you can build both modern and legacy bundles using cli args: 23 | 24 | ``` 25 | jetpack build --modern --legacy 26 | jetpack build -ml 27 | ``` 28 | 29 | Or by configuring it in your `jetpack.config.js`: 30 | 31 | ``` 32 | module.exports = { 33 | target: { 34 | modern: true, 35 | legacy: true 36 | } 37 | } 38 | ``` 39 | 40 | To tweak what browsers are considered modern and legacy, use `.browserslistrc` with `modern` and `legacy` environments: 41 | 42 | ``` 43 | [modern] 44 | > 10% 45 | 46 | [legacy] 47 | > 0.1% 48 | ``` 49 | 50 | Running `jetpack build` with legacy turned on will produce `index.html` pointing to `bundle.js` and `index.legacy.html` pointing to `bundle.legacy.js`. You will need a way to serve the right index file to each browser. 51 | 52 | ## Differential serving 53 | 54 | Jetpack opts to not use module/no module approach due to it's 2 limitations: 55 | 56 | 1. Currently module/no module option in `@babel/preset-env` transpiles async/await into regenerator and that's not desired for modern browsers. 57 | 2. The browsers that support ES modules will eventually get old, and by using browser detection to serve the right bundle we can keep transpiling less and less over time. 58 | 59 | To do the differential serving you can use the built in `jetpack/serve` module or the standalone [`jetpack-serve`](https://github.com/KidkArolis/jetpack-serve) package. The package is recommended in production, since that allows installing jetpack as dev dependency. 60 | 61 | ### jetpack/serve module 62 | 63 | ```js 64 | const express = require('express') 65 | const jetpack = require('jetpack/serve') 66 | 67 | const app = express() 68 | 69 | app.get('/api/data', (req, res) => { 70 | res.send('hello') 71 | }) 72 | 73 | // this will work in both production and development 74 | // in development it proxies to jetpack's dev serve 75 | // in production it will detect if browser is modern 76 | // or legacy and serve an appropriate entry point 77 | app.get('*', jetpack) 78 | 79 | app.listen(3000, function () { 80 | console.log('Running server on http://localhost:3000') 81 | }) 82 | ``` 83 | 84 | This is the most convenient option, but can be undesirable if you'd like to avoid shipping the entire jetpack package with all of it's dependencies (i.e. `rspack`, `swc`, `sass-embedded`, etc.) to your production apps. To avoid that, consider installing `jetpack` as a dev dependency and using the standalone `jetpack-serve` package instead. 85 | 86 | ### jetpack-serve package 87 | 88 | ```js 89 | const express = require('express') 90 | const jetpack = require('jetpack-serve') 91 | 92 | const app = express() 93 | 94 | app.get('/api/data', (req, res) => { 95 | res.send('hello') 96 | }) 97 | 98 | // this will work in both production and development 99 | // in development it will require('jetpack/serve'), so 100 | // you will have to have jetpack installed 101 | // in production it will detect if browser is modern 102 | // or legacy and serve an appropriate entry point and use 103 | // express.static for the actual assets 104 | app.use(jetpack()) 105 | 106 | app.listen(3000, function () { 107 | console.log('Running server on http://localhost:3000') 108 | }) 109 | ``` 110 | 111 | Or if you're using something other than express or want to customise the behaviour, you can use the UA regex directly: 112 | 113 | ```js 114 | const jetpack = require('jetpack-serve') 115 | 116 | module.exports = function handle(req, res) { 117 | const isModern = jetpack.regexp({ modern: true }).test(req.headers['user-agent']) 118 | if (isModern) { 119 | // serve modern 120 | } else { 121 | // serve legacy 122 | } 123 | } 124 | ``` 125 | 126 | ## Browsers command 127 | 128 | To see what browsers your modern or legacy bundles will target, jetpack provides a `jetpack browsers` command: 129 | 130 | ``` 131 | $ jetpack browsers 132 | 133 | [modern query] 134 | > 0.5% and last 2 versions 135 | 136 | [modern browsers] 137 | chrome 74 138 | edge 18 139 | edge 17 140 | firefox 66 141 | firefox 60 142 | 143 | [modern coverage globally] 144 | 77.88% 145 | ``` 146 | -------------------------------------------------------------------------------- /docs/06-workflow-and-deployment.md: -------------------------------------------------------------------------------- 1 | # Workflow and Deployment 2 | 3 | Very often when you are developing a client side web application, you are also building an accompanying API. Jetpack was created to specifically help in that scenario. 4 | 5 | Sometimes an application is just a quick and simple tool, say an internal tool for your company. In which case you don't need fancy deployment, you just need to get things done quickly. 6 | 7 | And sometimes, you want the app to be fast to users around the world. 8 | 9 | In this article we look at several approaches you can take when developing and deploying your apps. 10 | 11 | ## Purely client side apps 12 | 13 | If you are building a purely static app. Things are simple: 14 | 15 | 1. Develop your app using `jetpack` dev server 16 | 2. When it's ready to be deployed, build with `jetpack build` 17 | 3. Deploy to your favorite static host, e.g. `netlifyctl deploy -b dist` 18 | 19 | ## Client and API in one 20 | 21 | For simple tools, e.g. internal business tools, it's very convenient to keep the code and deployment all in one place. 22 | 23 | In your `package.json`, make sure `"main"` points to your server entry point. This way it's easy to run your server by executing `node .` or `nodemon `. 24 | 25 | In your `jetpack.config.js` configure `entry` to point to your client side entry point. This way it's easy to run your jetpack dev server by executing `jetpack`. 26 | 27 | Because we'd like to keep the deployment of this as simple as possible, we will be serving our client side assets via our API server. To do that, you can make use of the `jetpack/serve` middleware. For example: 28 | 29 | ```js 30 | const express = require('express') 31 | const jetpack = require('jetpack/serve') 32 | 33 | const app = express(); 34 | 35 | app.get('/api/unicorns', (req, res) => {...}) 36 | app.get('*', jetpack) 37 | ``` 38 | 39 | Note: here we're using express, but it's possible to plug `jetpack/serve` into any framework that has node's `req` and `res` available. 40 | 41 | Now we're ready to develop this application. To run both your client dev server and the API server at once, you can execute: 42 | 43 | $ jetpack --exec 44 | 45 | or 46 | 47 | $ jetpack -x 48 | 49 | But often, it's most convenient to use a split terminal and in one run: 50 | 51 | $ jetpack 52 | 53 | And in the other run: 54 | 55 | $ nodemon . 56 | 57 | This way we get both client and server to restart on every code change. 58 | 59 | What does `jetpack/serve` do? In development it proxies to the jetpack dev server. In production, it efficiently serves your built assets from the `dist` directory. 60 | 61 | To deploy this project to a server you would just run `jetpack build` and use the entry point of `node .` to run your application. In Docker, for example, it would look like this: 62 | 63 | ``` 64 | FROM node:10 65 | 66 | WORKDIR /app 67 | 68 | COPY package.json ./ 69 | COPY package-lock.json ./ 70 | 71 | RUN npm install 72 | 73 | COPY . . 74 | 75 | CMD [ "node", "." ] 76 | ``` 77 | 78 | # Deploying Client and Server separately 79 | 80 | If you're not using node.js for your server, or if you're working on an application where performance is important, it's best to deploy your client side assets to a CDN, separetly from your API server. 81 | 82 | When making requests from your web app into your API, you might choose to request the full host, e.g.: 83 | 84 | - `fetch('http://localhost:3000/unicorns')` in development 85 | - `fetch('https://api.myapp.com/unicorns')` in production 86 | 87 | This way you might need to configure CORS headers. 88 | 89 | Or you might request your API relative, e.g.: 90 | 91 | - `fetch('/api/unicorns')` in both development and production 92 | 93 | If you're doing the latter, then you could employ jetpack's proxy feature, in your `jetpack.config.js`: 94 | 95 | ```js 96 | module.exports = { 97 | proxy: { 98 | '/api/*': 'http://localhost:3000' 99 | } 100 | } 101 | ``` 102 | 103 | To deploy this, you would now deploy your assets to a CDN and your API to an application server separately. 104 | 105 | For example, when using [Netlify](https://www.netlify.com/) and [Dokku](http://dokku.viewdocs.io/dokku/): 106 | 107 | # release the client side app 108 | $ jetpack build 109 | $ cp _redirects dist 110 | $ netlifyctl deploy -b dist 111 | 112 | # release the server side api 113 | $ git push dokku master 114 | 115 | Note: we're using Netlify's [_redirects](https://www.netlify.com/docs/redirects/) feature in this case to proxy the requests to your API server deployed via Dokku. 116 | 117 | Alternatively, some platforms, such as [Now](https://zeit.co/now) support both static assets and APIs, in that case, you could deploy this app using a `now.json` config like this: 118 | 119 | ``` 120 | { 121 | "version": 2, 122 | "builds": [ 123 | { "src": "server/index.js", "use": "@now/node-server" }, 124 | { "src": "package.json", "use": "@now/static-build" } 125 | ], 126 | "routes": [ 127 | { "src": "/api/*", "dest": "/server/index.js" } 128 | ] 129 | } 130 | ``` 131 | 132 | ## Conclusion 133 | 134 | I hope this shed a little bit of light on why jetpack was created. It tries to fit into all of these different development workflows, whilst staying very light on configuration. If you find this article interesting, but got confused and would like me to expand, post an issue and let me know! 135 | -------------------------------------------------------------------------------- /lib/reporter.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const chalk = require('picocolors') 3 | 4 | module.exports = function reporter(compiler, log, options = {}) { 5 | let builds = 0 6 | 7 | function onError(errors) { 8 | const sfx = errors.length > 1 ? 's' : '' 9 | log.error(`Failed to compile! Found ${chalk.bold(chalk.red(errors.length))} error${sfx}:`) 10 | errors.forEach((x) => console.log('\n ' + x.replace(/(\r?\n)/g, '$1 ') + '\n')) 11 | } 12 | 13 | function onWarning(warnings) { 14 | const sfx = warnings.length > 1 ? 's' : '' 15 | log.warn(`Compiled with ${chalk.bold(chalk.yellow(warnings.length))} warning${sfx}:`) 16 | warnings.forEach((x) => console.log('\n ' + x.replace(/(\r?\n)/g, '$1 ') + '\n')) 17 | } 18 | 19 | compiler.hooks.invalid.tap('jetpack', (file) => { 20 | file = path.relative(options.dir, file) 21 | log.info(`File changed: ${chalk.bold(chalk.white(file))}`) 22 | }) 23 | 24 | compiler.hooks.failed.tap('jetpack', (error) => { 25 | log.error('Failed to compile!', error) 26 | }) 27 | 28 | compiler.hooks.done.tap('jetpack', (stats) => { 29 | builds++ 30 | 31 | const format = require('webpack-format-messages') 32 | 33 | const { errors, warnings } = format(stats) 34 | 35 | if (errors.length > 0) { 36 | return onError(errors) 37 | } 38 | 39 | if (warnings.length > 0) { 40 | onWarning(warnings) 41 | } 42 | 43 | if (options.printAssets) { 44 | printAssets(stats.toJson(), log) 45 | } 46 | 47 | log.info(`${builds > 1 ? 'Rebuilt' : 'Built'} in ${formatTime(stats.endTime - stats.startTime)}`) 48 | }) 49 | } 50 | 51 | const getText = (arr, row, col) => { 52 | return arr[row][col].value 53 | } 54 | 55 | const table = (array, align, log) => { 56 | const rows = array.length 57 | const cols = array[0].length 58 | const colSizes = new Array(cols) 59 | for (let col = 0; col < cols; col++) colSizes[col] = 0 60 | for (let row = 0; row < rows; row++) { 61 | for (let col = 0; col < cols; col++) { 62 | const value = `${getText(array, row, col)}` 63 | if (value.length > colSizes[col]) { 64 | colSizes[col] = value.length 65 | } 66 | } 67 | } 68 | for (let row = 0; row < rows; row++) { 69 | let buf = '' 70 | for (let col = 0; col < cols; col++) { 71 | const format = (...args) => { 72 | buf += array[row][col].color(...args) 73 | } 74 | const value = `${getText(array, row, col)}` 75 | let l = value.length 76 | if (align[col] === 'l') format(value) 77 | if (col !== cols - 1) { 78 | for (; l < colSizes[col]; l++) buf += chalk.white(' ') 79 | } 80 | if (align[col] === 'r') format(value) 81 | if (col + 1 < cols && colSizes[col] !== 0) { 82 | buf += chalk.white(' ') 83 | } 84 | } 85 | console.log(' ' + buf) 86 | } 87 | } 88 | 89 | const getAssetColor = (asset, defaultColor) => { 90 | if ( 91 | asset.name.endsWith('.js.map') || 92 | asset.name.endsWith('.hot-update.js') || 93 | asset.name.endsWith('.hot-update.json') 94 | ) { 95 | return chalk.gray 96 | } 97 | 98 | if (asset.isOverSizeLimit) { 99 | return chalk.yellow 100 | } 101 | 102 | return defaultColor 103 | } 104 | 105 | function printAssets(obj, log) { 106 | const modules = {} 107 | obj.modules.forEach((module) => { 108 | module.chunks.forEach((chunk) => { 109 | modules[chunk] = modules[chunk] || 0 110 | modules[chunk] += 1 111 | }) 112 | }) 113 | 114 | const assets = obj.assets 115 | .sort((a, b) => { 116 | return a.name > b.name ? 1 : -1 117 | }) 118 | .sort((a, b) => { 119 | const aExt = a.name.split('.')[a.name.split('.').length - 1] 120 | const bExt = b.name.split('.')[b.name.split('.').length - 1] 121 | return aExt > bExt ? 1 : -1 122 | }) 123 | 124 | if (assets && obj.assets.length > 0) { 125 | const t = [ 126 | [ 127 | { 128 | value: 'Asset', 129 | color: chalk.bold 130 | }, 131 | { 132 | value: 'Modules', 133 | color: chalk.bold 134 | }, 135 | { 136 | value: 'Size', 137 | color: chalk.bold 138 | } 139 | ] 140 | ] 141 | for (const asset of assets) { 142 | t.push([ 143 | { 144 | value: asset.name, 145 | color: getAssetColor(asset, chalk.white) 146 | }, 147 | { 148 | value: 149 | asset.name.endsWith('.js') && !asset.name.endsWith('.hot-update.js') && !asset.name.startsWith('runtime~') 150 | ? asset.chunks.reduce((acc, chunk) => acc + modules[chunk], 0) 151 | : '-', 152 | color: 153 | asset.name.endsWith('.js') && !asset.name.endsWith('.hot-update.js') && !asset.name.startsWith('runtime~') 154 | ? getAssetColor(asset, chalk.white) 155 | : chalk.gray 156 | }, 157 | { 158 | value: formatSize(asset.size), 159 | color: getAssetColor(asset, chalk.white) 160 | } 161 | ]) 162 | } 163 | console.log('') 164 | table(t, 'lll', log) 165 | console.log('') 166 | } 167 | } 168 | 169 | function formatSize(size) { 170 | if (size <= 0) { 171 | return '0 bytes' 172 | } 173 | const abbreviations = ['bytes', 'KiB', 'MiB', 'GiB'] 174 | const index = Math.floor(Math.log(size) / Math.log(1024)) 175 | return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${abbreviations[index]}` 176 | } 177 | 178 | function formatTime(ms = 0) { 179 | return (ms / 1000).toFixed(2) + 's' 180 | } 181 | -------------------------------------------------------------------------------- /docs/01-configuration-options.md: -------------------------------------------------------------------------------- 1 | # Configuration Options 2 | 3 | ## CLI 4 | 5 | Jetpack accepts some configuration via CLI. 6 | 7 | ``` 8 | $ jetpack --help 9 | 10 | Usage: jetpack [options] [command] [path] 11 | 12 | Options: 13 | -v, --version print the version of jetpack and rspack 14 | -p, --port port, defaults to 3030 15 | -d, --dir [path] run jetpack in the context of this directory 16 | -c, --config config file to use, defaults to jetpack.config.js 17 | -r, --no-hot disable hot reloading 18 | -u, --no-minify disable minification 19 | -m, --modern build a modern bundle 20 | -l, --legacy build a legacy bundle 21 | -x, --exec [path] execute an additional process, e.g. an api server 22 | -i, --print-config print the rspack config object used in the current command 23 | --log [levels] select log levels: info, progress, none (default: "info,progress") 24 | -h, --help display help for command 25 | 26 | Commands: 27 | dev run the dev server 28 | build build for production 29 | inspect analyze bundle 30 | browsers [options] print supported browsers 31 | clean remove the dist dir 32 | help [command] display help for command 33 | ``` 34 | 35 | ## Configuration File 36 | 37 | Jetpack can also be configured using `jetpack.config.js` file. Here are all of the available options. 38 | 39 | ```js 40 | module.exports = { 41 | // directory to run jetpack in 42 | dir: '.', 43 | 44 | // entry module path relative to dir 45 | // defaults to which ever is found first: 46 | // index.js 47 | // package.json#main 48 | // src/index.js 49 | entry: '.', 50 | 51 | // port of the dev server 52 | port: 3030, 53 | 54 | // relative path to static assets file dir 55 | // these are files that you don't want to process via rspack 56 | // but want to serve as part of your application, these 57 | // will get exposed under /assets/* 58 | static: 'assets', 59 | 60 | // build output path relative to dir 61 | dist: 'dist', 62 | 63 | // use when uploading assets to CDN, e.g. 'https://storage.googleapis.com/humaans-static/assets/' 64 | // or when serving from a different path than the jetpack default. Note: this doesn't affect 65 | // the build output structure, you will still get dist/index.html and dist/assets/*, but 66 | // manifest.json and index.html will be pointing to this publicPath instead of the default /assets 67 | publicPath: '/assets/', 68 | 69 | // jsx pragma 70 | // defaults to `React.createElement` if react is installed, `h` otherwise 71 | jsx: 'React.createElement', 72 | 73 | // hot reloading 74 | hot: true, 75 | 76 | // unified flag for source maps for js and css 77 | // follows rspack naming, but can also be simply set to true 78 | // it's true by default in development and false in production 79 | sourceMaps: true, 80 | 81 | // in you need to turn off minification in production for any reason 82 | minify: false, 83 | 84 | // set to `true` to enable retries on chunk loads 85 | // defaults to trying 5 times with exponential backoff 86 | chunkLoadRetry: false, 87 | 88 | // command executed to run the server/api process 89 | // this command is executed only if `-x` arg is passed to jetpack 90 | // even if this option is configured 91 | exec: 'node .', 92 | 93 | // used for proxying certain requests to a different server 94 | // e.g. { '/api/*': 'http://localhost:3000', 95 | // '/api2/*': 'http://localhost:3001/:splat', 96 | // '/api3/*': 'http://localhost:3002/custom/:splat' } 97 | // it can also be a function that receives an express app 98 | // e.g. (app) => app.get('/api/foo', (req, res) => {...}) 99 | proxy: {}, 100 | 101 | // configure logging 102 | log: 'info,progress', 103 | 104 | // the index.html template generation 105 | // defaults to package.json#name or 'jetpack' if not available 106 | title: 'jetpack', 107 | 108 | // useful for adding meta tags or scripts 109 | // can be specified in handlebars template syntax 110 | head: null, 111 | 112 | // body 113 | // can be specified in handlebars template syntax 114 | body: `
`, 115 | 116 | // the html template 117 | // can be specified in handlebars template syntax 118 | html: `see lib/template.hbs`, 119 | 120 | // css options 121 | css: { 122 | // css modules 123 | modules: false, 124 | 125 | // a shortcut for setting lightningcss feature flags 126 | // e.g. { 'nesting': true, colorFunction: true } 127 | // see https://rspack.dev/guide/features/builtin-lightningcss-loader#options 128 | // and https://lightningcss.dev/transpilation.html 129 | features: { 130 | include: {}, 131 | exclude: {}, 132 | }, 133 | 134 | // when using Sass, you can specify paths to your global scss resources 135 | // so that you can use your shared variables & mixins across all Sass styles 136 | // without manually importing them in each file. Works with CSS Modules. 137 | // See further tips: https://github.com/shakacode/sass-resources-loader#tips 138 | resources: [] 139 | }, 140 | 141 | target: { 142 | modern: true, 143 | legacy: false 144 | }, 145 | 146 | // rspack config transform fn 147 | rspack: (config, options) => { 148 | // config is the rspack config generated by jetpack 149 | // options is this jetpack options object including defaults, 150 | // but also includes a very handy options.production flag 151 | // see 02-customizing-rspack.md for more details 152 | } 153 | } 154 | ``` 155 | 156 | ## HTML Template 157 | 158 | The default html template is the following: 159 | 160 | ```hbs 161 | 162 | 163 | 164 | 165 | 166 | {{{title}}} 167 | {{#each assets.css}} 168 | 169 | {{/each}} 170 | {{{head}}} 171 | 172 | 173 | {{{body}}} 174 | {{#if runtime}} 175 | 178 | {{/if}} 179 | {{#each assets.js}} 180 | 181 | {{/each}} 182 | 183 | 184 | ``` 185 | 186 | You can override it completely using the `html` option or extend it by using `head` and `body` options. 187 | 188 | ## Modules 189 | 190 | Jetpack exports the following modules: 191 | 192 | ### jetpack/serve 193 | 194 | It's a middleware that can serve your assets in development and production. It proxies to jetpack dev server in development and serves files from `dist` in production. For example: 195 | 196 | ```js 197 | const express = require('express') 198 | const jetpack = require('jetpack/serve') 199 | 200 | const app = express(); 201 | 202 | app.get('/api/unicorns', (req, res) => {...}) 203 | app.get('*', jetpack) 204 | ``` 205 | 206 | ### jetpack/options 207 | 208 | Receive all of the jepack config. Might be useful if you want to look at the port, dist, or generated assets in production if you're say generating your HTML server side, e.g.: 209 | 210 | ``` 211 | const options = require('jetpack/options') 212 | 213 | options.production 214 | options.entry 215 | options.port 216 | options.assets 217 | 218 | options.assets.js.forEach(script => console.log(script)) 219 | options.assets.css.forEach(script => console.log(script)) 220 | options.assets.other 221 | options.assets.runtime // the path to the runtime 222 | options.runtime // the content of the runtime script 223 | ``` 224 | 225 | ### jetpack/proxy 226 | 227 | A simple proxy helper, useful if you want to customize your proxy behaviour using a function. E.g. 228 | 229 | ```js 230 | const proxy = require('jetpack/proxy') 231 | 232 | module.exports = { 233 | proxy: (app) => { 234 | app.post('/custom', (req, res) => res.send(422)) 235 | app.get('/api/*', proxy('http://localhost:3000')) 236 | } 237 | } 238 | ``` 239 | 240 | ### jetpack/rspack 241 | 242 | An export of the rspack module used by jetpack. Useful to access rspack's plugins, etc. 243 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | const { existsSync, readFileSync } = require('fs') 2 | const path = require('path') 3 | 4 | const SUPPORTED_CONFIG_FILES = ['jetpack.config.js', 'jetpack.config.cjs'] 5 | 6 | module.exports = function options(command = 'dev', { entry = null, flags = {} } = {}) { 7 | const dir = flags.dir ? path.resolve(flags.dir) : process.cwd() 8 | const production = command === 'build' || command === 'inspect' 9 | 10 | // set this so that the config file could correctly 11 | // determine if we're in production mode 12 | if (production && !process.env.NODE_ENV) { 13 | process.env.NODE_ENV = 'production' 14 | } 15 | 16 | const configPath = flags.config || SUPPORTED_CONFIG_FILES.find((file) => existsSync(path.join(dir, file))) 17 | 18 | const configFromFile = readConfigFromFile(dir, configPath) 19 | const options = Object.assign( 20 | {}, 21 | configFromFile, 22 | pick(flags, ['port', 'dir', 'exec', 'hot', 'config', 'minify', 'log']) 23 | ) 24 | 25 | // if specified in config file 26 | if (!entry) { 27 | entry = options.entry 28 | } 29 | 30 | if (!entry) { 31 | entry = first( 32 | [ 33 | // default – node style 34 | '.', 35 | // for rspack compatibility 36 | './src' 37 | ], 38 | ifModuleExists(dir) 39 | ) 40 | } 41 | 42 | // if nothing is found, default to '.' in case 43 | // the entry module is created after jetpack starts 44 | if (!entry) { 45 | entry = '.' 46 | } 47 | 48 | // when tabing in the terminal to autocomplete paths 49 | // the beginning of the path doesn't start with ./ 50 | // and rspack tries to resolve it as a node module 51 | // always prefix the entry with ./ unless it's an absolute path 52 | if (entry !== '.' && !entry.startsWith('/') && !entry.startsWith('./')) { 53 | entry = './' + entry 54 | } 55 | 56 | const dist = options.dist || 'dist' 57 | const publicPath = options.publicPath || '/assets/' 58 | 59 | const target = 60 | flags.legacy || flags.modern 61 | ? { modern: !!flags.modern, legacy: !!flags.legacy } 62 | : options.target || { modern: true, legacy: false } 63 | 64 | return clean({ 65 | // build mode 66 | production, 67 | 68 | // directory to run jetpack in 69 | dir, 70 | 71 | // entry module path relative to dir 72 | entry, 73 | 74 | // port of the dev server 75 | port: options.port === undefined ? 3030 : options.port, 76 | 77 | // relative path to static assets file dir 78 | static: options.static || 'assets', 79 | 80 | // relative path to output dist dir 81 | dist, 82 | 83 | publicPath, 84 | 85 | // are we using react 86 | react: options.react || isUsingReact(dir), 87 | 88 | // hot reloading 89 | hot: options.hot === undefined ? true : options.hot, 90 | 91 | // unified flag for source maps for js and css 92 | sourceMaps: 93 | options.sourceMaps === undefined 94 | ? production 95 | ? undefined 96 | : 'source-map' 97 | : options.sourceMaps === true 98 | ? 'source-map' 99 | : options.sourceMaps, 100 | 101 | // to turn off minification in production 102 | minify: options.minify === undefined ? true : options.minify, 103 | 104 | // retry loading chunks 105 | chunkLoadRetry: options.chunkLoadRetry ?? false, 106 | 107 | target, 108 | 109 | // command executed to run the server/api process 110 | exec: flags.exec ? first([flags.exec, configFromFile.exec, 'node .'], ifString) : false, 111 | 112 | printConfig: flags.printConfig, 113 | 114 | // used for proxying certain requests to a different server 115 | // can be an object or a function 116 | proxy: options.proxy || {}, 117 | 118 | // log levels to output 119 | logLevels: parseLogLevels(options.log), 120 | 121 | // url paths to all of the entrypoints files to be embedded into the html 122 | assets: { 123 | js: [target.modern ? '/assets/bundle.js' : '/assets/bundle.legacy.js'], 124 | css: [], 125 | runtime: [], 126 | other: [] 127 | }, 128 | 129 | // the runtime code to be embedded into the html 130 | runtime: null, 131 | 132 | // the index.html template generation 133 | title: options.title || pkg(dir).name || 'jetpack', 134 | 135 | // useful for meta tags and scripts 136 | head: options.head || null, 137 | 138 | // body 139 | body: options.body || "
", 140 | 141 | // the html template 142 | html: options.html || readFileSync(path.join(__dirname, 'template.hbs')).toString(), 143 | 144 | css: Object.assign( 145 | { 146 | // css modules 147 | modules: false, 148 | 149 | // a shortcut for setting lightningcss-loader features 150 | features: { 151 | include: null, 152 | exclude: null 153 | } 154 | }, 155 | options.css 156 | ), 157 | 158 | // for browsers command 159 | coverage: flags.coverage || false, 160 | 161 | // rspack config transform fn 162 | rspack: options.rspack || options.webpack 163 | }) 164 | } 165 | 166 | module.exports.recomputeAssets = function recomputeAssets(options, stats) { 167 | const { outputPath, publicPath, entrypoints } = stats 168 | return Object.assign({}, options, { 169 | assets: assets({ outputPath, publicPath, entrypoints }), 170 | runtime: runtime({ outputPath, publicPath, entrypoints }) 171 | }) 172 | } 173 | 174 | function clean(obj) { 175 | return Object.keys(obj).reduce(function (memo, k) { 176 | if (obj[k] === undefined) return memo 177 | memo[k] = obj[k] 178 | return memo 179 | }, {}) 180 | } 181 | 182 | function readConfigFromFile(dir, configFilePath) { 183 | if (!configFilePath) return {} 184 | const configPath = path.join(dir, configFilePath) 185 | const exists = existsSync(configPath) 186 | const config = exists ? require(configPath) : {} 187 | return config.default ? config.default : config 188 | } 189 | 190 | function pkg(dir) { 191 | try { 192 | return JSON.parse(readFileSync(path.join(dir, 'package.json'))) 193 | } catch (err) { 194 | return {} 195 | } 196 | } 197 | 198 | function isUsingReact(dir) { 199 | try { 200 | const pkg = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8')) 201 | return (pkg.dependencies && pkg.dependencies.react) || (pkg.devDependencies && pkg.devDependencies.react) 202 | } catch (err) { 203 | return false 204 | } 205 | } 206 | 207 | function assets({ publicPath, entrypoints }) { 208 | const js = [] 209 | const css = [] 210 | const other = [] 211 | const runtime = [] 212 | 213 | // process all of the entrypoints into a format 214 | // that is easy to embed into the template 215 | // where we inline the runtime, outpu css as link tags 216 | // and js as script tags 217 | entrypoints.bundle.assets.forEach(({ name: asset }) => { 218 | const assetPath = publicPath + asset 219 | if (asset.startsWith('runtime~bundle') && asset.endsWith('.js')) { 220 | runtime.push(assetPath) 221 | } else if (asset.endsWith('.js')) { 222 | js.push(assetPath) 223 | } else if (asset.endsWith('.css')) { 224 | css.push(assetPath) 225 | } else { 226 | other.push(assetPath) 227 | } 228 | }) 229 | 230 | return { js, css, runtime, other } 231 | } 232 | 233 | function runtime({ outputPath, publicPath, entrypoints }) { 234 | let runtime = null 235 | 236 | if (entrypoints && entrypoints.bundle) { 237 | const runtimeAsset = entrypoints.bundle.assets.find((a) => a.name.startsWith(`runtime~bundle.`)) 238 | if (runtimeAsset) { 239 | try { 240 | runtime = String(readFileSync(path.join(outputPath, runtimeAsset.name))) 241 | // Since we inline the runtime at the root index html, correct the sourceMappingURL 242 | return runtime.replace('//# sourceMappingURL=', `//# sourceMappingURL=${publicPath}`) 243 | } catch (err) { 244 | return null 245 | } 246 | } 247 | } 248 | 249 | return null 250 | } 251 | 252 | function pick(obj, attrs) { 253 | return attrs.reduce((acc, attr) => { 254 | if (obj[attr] !== undefined) { 255 | acc[attr] = obj[attr] 256 | } 257 | return acc 258 | }, {}) 259 | } 260 | 261 | function first(values, condition) { 262 | for (const val of values) { 263 | if (condition(val)) { 264 | return val 265 | } 266 | } 267 | } 268 | 269 | function ifModuleExists(dir) { 270 | return function (mod) { 271 | if (!mod) { 272 | return false 273 | } 274 | 275 | try { 276 | require.resolve(path.join(dir, mod)) 277 | } catch (err) { 278 | if (err.code === 'MODULE_NOT_FOUND') { 279 | return false 280 | } 281 | throw err 282 | } 283 | return mod 284 | } 285 | } 286 | 287 | function ifString(str) { 288 | if (typeof str === 'string') { 289 | return str 290 | } else { 291 | return false 292 | } 293 | } 294 | 295 | function parseLogLevels(input) { 296 | const levels = (input || '').split(',').map((l) => l.trim()) 297 | const result = {} 298 | for (const level of ['info', 'progress', 'none']) { 299 | result[level] = levels.includes(level) 300 | } 301 | return result 302 | } 303 | -------------------------------------------------------------------------------- /examples/with-react/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-react", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "with-react", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "react": "^19.2.0", 13 | "react-dom": "^19.2.0", 14 | "react-hot-loader": "^4.13.1" 15 | } 16 | }, 17 | "node_modules/big.js": { 18 | "version": "5.2.2", 19 | "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", 20 | "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", 21 | "license": "MIT", 22 | "engines": { 23 | "node": "*" 24 | } 25 | }, 26 | "node_modules/dom-walk": { 27 | "version": "0.1.2", 28 | "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", 29 | "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" 30 | }, 31 | "node_modules/emojis-list": { 32 | "version": "3.0.0", 33 | "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", 34 | "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", 35 | "license": "MIT", 36 | "engines": { 37 | "node": ">= 4" 38 | } 39 | }, 40 | "node_modules/fast-levenshtein": { 41 | "version": "2.0.6", 42 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 43 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 44 | "license": "MIT" 45 | }, 46 | "node_modules/global": { 47 | "version": "4.4.0", 48 | "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", 49 | "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", 50 | "license": "MIT", 51 | "dependencies": { 52 | "min-document": "^2.19.0", 53 | "process": "^0.11.10" 54 | } 55 | }, 56 | "node_modules/hoist-non-react-statics": { 57 | "version": "3.3.2", 58 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", 59 | "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", 60 | "license": "BSD-3-Clause", 61 | "dependencies": { 62 | "react-is": "^16.7.0" 63 | } 64 | }, 65 | "node_modules/js-tokens": { 66 | "version": "4.0.0", 67 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 68 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 69 | "license": "MIT" 70 | }, 71 | "node_modules/json5": { 72 | "version": "2.2.3", 73 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 74 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 75 | "license": "MIT", 76 | "bin": { 77 | "json5": "lib/cli.js" 78 | }, 79 | "engines": { 80 | "node": ">=6" 81 | } 82 | }, 83 | "node_modules/loader-utils": { 84 | "version": "2.0.4", 85 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", 86 | "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", 87 | "license": "MIT", 88 | "dependencies": { 89 | "big.js": "^5.2.2", 90 | "emojis-list": "^3.0.0", 91 | "json5": "^2.1.2" 92 | }, 93 | "engines": { 94 | "node": ">=8.9.0" 95 | } 96 | }, 97 | "node_modules/loose-envify": { 98 | "version": "1.4.0", 99 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 100 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 101 | "license": "MIT", 102 | "dependencies": { 103 | "js-tokens": "^3.0.0 || ^4.0.0" 104 | }, 105 | "bin": { 106 | "loose-envify": "cli.js" 107 | } 108 | }, 109 | "node_modules/min-document": { 110 | "version": "2.19.2", 111 | "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", 112 | "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", 113 | "license": "MIT", 114 | "dependencies": { 115 | "dom-walk": "^0.1.0" 116 | } 117 | }, 118 | "node_modules/object-assign": { 119 | "version": "4.1.1", 120 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 121 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 122 | "license": "MIT", 123 | "engines": { 124 | "node": ">=0.10.0" 125 | } 126 | }, 127 | "node_modules/process": { 128 | "version": "0.11.10", 129 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 130 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 131 | "license": "MIT", 132 | "engines": { 133 | "node": ">= 0.6.0" 134 | } 135 | }, 136 | "node_modules/prop-types": { 137 | "version": "15.8.1", 138 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", 139 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", 140 | "license": "MIT", 141 | "dependencies": { 142 | "loose-envify": "^1.4.0", 143 | "object-assign": "^4.1.1", 144 | "react-is": "^16.13.1" 145 | } 146 | }, 147 | "node_modules/react": { 148 | "version": "19.2.0", 149 | "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", 150 | "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", 151 | "license": "MIT", 152 | "engines": { 153 | "node": ">=0.10.0" 154 | } 155 | }, 156 | "node_modules/react-dom": { 157 | "version": "19.2.0", 158 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", 159 | "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", 160 | "license": "MIT", 161 | "dependencies": { 162 | "scheduler": "^0.27.0" 163 | }, 164 | "peerDependencies": { 165 | "react": "^19.2.0" 166 | } 167 | }, 168 | "node_modules/react-hot-loader": { 169 | "version": "4.13.1", 170 | "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.1.tgz", 171 | "integrity": "sha512-ZlqCfVRqDJmMXTulUGic4lN7Ic1SXgHAFw7y/Jb7t25GBgTR0fYAJ8uY4mrpxjRyWGWmqw77qJQGnYbzCvBU7g==", 172 | "license": "MIT", 173 | "dependencies": { 174 | "fast-levenshtein": "^2.0.6", 175 | "global": "^4.3.0", 176 | "hoist-non-react-statics": "^3.3.0", 177 | "loader-utils": "^2.0.3", 178 | "prop-types": "^15.6.1", 179 | "react-lifecycles-compat": "^3.0.4", 180 | "shallowequal": "^1.1.0", 181 | "source-map": "^0.7.3" 182 | }, 183 | "engines": { 184 | "node": ">= 6" 185 | }, 186 | "peerDependencies": { 187 | "@types/react": "^15.0.0 || ^16.0.0 || ^17.0.0", 188 | "react": "^15.0.0 || ^16.0.0 || ^17.0.0", 189 | "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0" 190 | }, 191 | "peerDependenciesMeta": { 192 | "@types/react": { 193 | "optional": true 194 | } 195 | } 196 | }, 197 | "node_modules/react-is": { 198 | "version": "16.13.1", 199 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 200 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 201 | "license": "MIT" 202 | }, 203 | "node_modules/react-lifecycles-compat": { 204 | "version": "3.0.4", 205 | "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", 206 | "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", 207 | "license": "MIT" 208 | }, 209 | "node_modules/scheduler": { 210 | "version": "0.27.0", 211 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", 212 | "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", 213 | "license": "MIT" 214 | }, 215 | "node_modules/shallowequal": { 216 | "version": "1.1.0", 217 | "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", 218 | "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", 219 | "license": "MIT" 220 | }, 221 | "node_modules/source-map": { 222 | "version": "0.7.6", 223 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", 224 | "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", 225 | "license": "BSD-3-Clause", 226 | "engines": { 227 | "node": ">= 12" 228 | } 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.3.0 2 | 3 | - Update to Rspack 1.6.4 4 | - Update all dependencies 5 | 6 | # 4.2.1 7 | 8 | - Update to Rspack 1.5.2 9 | - Update all dependencies 10 | 11 | # 4.2.0 12 | 13 | - Update to Rspack 1.5 14 | - Update all dependencies to remove vulnerabilities 15 | - Replace the use of `inquirer-confirm` dependency with a local implementation 16 | - Remove the use of `fs-extra` and `require-relative` in favor of Node built in modules 17 | 18 | # 4.1.0 19 | 20 | - Make rspack config creation synchronous 21 | - Use `jetpack/rspack.config.js` to import the full rspack config object as used by jetpack 22 | 23 | # 4.0.0 24 | 25 | - Add `typescript` support, compiled by `builtin:swc-loader` 26 | - Use `core-js@3.40` to correctly include latest polyfills 27 | - Switch to `builtin:swc-loader` 28 | - Make it easier to import global css when using css modules - css in *.global.css or node_modules no longer considered as css modules 29 | - Remove `h` as the default jsx pragma, instead use swc-loader's automatic mode for react 30 | - Upgrade to express@5 31 | 32 | # 3.1.0 33 | 34 | - Removed `commander` in favor of native Node.js `parseArgs` util, this might have slight affect on cli flags 35 | - Upgrade all dependencies, including `rspack@1.1.2` 36 | - Switch `core-js` to usage mode for smaller bundles as less and less `core-js` is needed for smaller browsers 37 | - Improve portability of `jetpack` - running it without installing it locally should work better now 38 | 39 | # 3.0.0 40 | 41 | **Replacing webpack with rspack! 🎉** 42 | 43 | - Breaking change: Replaces `webpack` with `rspack` - this adds a significant performance boost to jetpack. This is largely backwards compatible. However, if you customise your webpack in jetpack.config.js you might need to read the rspack [migration guides](https://rspack.dev/guide/migration/webpack). 44 | - Breaking change: Replaces `postcss` with the faster `lightningcss` via rspack's builtin loaders. It serves the same purpose of lowering css syntax for older browsers. 45 | - Upgrade `sass-loader` to use the modern `sass-embedded` which is significantly faster, this should be backwards compatible, but expect sass warnings if you're using older sass syntax. 46 | 47 | # 2.1.0 48 | 49 | - Adds `chunkLoadRetry` option for reloading chunks 50 | - Upgrade all dependencies 51 | 52 | # 2.0.0 53 | 54 | - Breaking change: styles are exported as named exports, so you must now write `import * as style from "./style.css"`, see https://github.com/webpack-contrib/css-loader/releases/tag/v7.0.0 for further details 55 | - Upgrade all dependencies 56 | 57 | # 1.4.0 58 | 59 | - Upgrade all dependencies 60 | - Add support for `jetpack.config.cjs` in addition to `jetpack.config.js` - helps in ESM native Node.js projects 61 | 62 | # 1.3.0 63 | 64 | - Upgrade all dependencies 65 | - Fix the `-x, -exec` command 66 | 67 | # 1.2.1 68 | 69 | - Fix: Correctly parse the `--config` command line arg. 70 | - Patch dependencies via npm audit 71 | 72 | # 1.2.0 73 | 74 | - Upgrade all dependencies 75 | - Remove preact example, since the example was out of date 76 | 77 | # 1.1.0 78 | 79 | - Upgrade all dependencies 80 | 81 | # 1.0.0 82 | 83 | - Jetpack has been used in prod for years now and deserves a 1.0.0 🥳 84 | 85 | # 0.30.0 86 | 87 | - Upgrade to webpack 5! 88 | - Switch to swc-loader from babel for super fast build times! 89 | - Log build progress (disable with `--no-progress` or `progress: false` in the config 90 | - Improve error handling, log unexpected errors when serving bundles in development instead of hanging 91 | - Remove the graceful termination fix introduced in 0.21.1 as it does not appear to work in node@16 92 | - Replace url-loader and file-loader with webpack 5 native asset support 93 | - Upgrade all dependencies 94 | - **Breaking:** switch to the latest browserslist defaults - this makes both `modern` and `legacy` builds the same by default, but you can still configure each one independently 95 | - **Breaking:** the runtime content is now referenced via `runtime` instead of `assets.runtime` in the template 96 | - **Breaking:** simplified logging behind `--log` flag, choose between `info`, `progress` or `none`, with default being `info,progress` 97 | - **Breaking:** removed support for react-hot-loader, you can still tweak your config to pull it in, but it is no longer automatically configured, use `react-refresh` instead! 98 | - **Breaking:** remove --jsx command line flag, use config instead 99 | 100 | # 0.22.0 101 | 102 | - Add support for optional chaining and nullish coalescing operator. This is supported by babel out of the box, but since jetpack is still on webpack 4 (it's faster?), we need to include the right plugins explicitly for this to work. 103 | - Upgrade all of the non breaking dependencies 104 | 105 | # 0.21.1 106 | 107 | - Fix graceful termination in `jetpack/serve`. In case the req is destroyed, make sure to also destroy the upstream proxyReq to the dev server, so that the request does not hold up the server from closing. 108 | 109 | # 0.21.0 110 | 111 | - Upgrade all dependencies. Except do not upgrade to webpack 5 just yet and do not upgrade plugins that dropped webpack 4 support. 112 | 113 | # 0.20.1 114 | 115 | - Fix proxy feature - proxy the query params correctly (issue #89) 116 | 117 | # 0.20.0 118 | 119 | - Upgrade all dependencies, this includes updating to `PostCSS 8` and includes a breaking change to the webpack config generated by jetpack around the `postcss-loader` 120 | 121 | # 0.19.0 122 | 123 | - Default to `fast-refresh` for React hot reloading if `react-hot-loader` is not installed in the project 124 | 125 | # 0.18.1 126 | 127 | - Install caniuse-lite as a project dependency, to force install the latest version globally in the dependency tree. Previously, a message "Browserslist: caniuse-lite is outdated. Please run next command `npm update`" would show up to jetpack's users. 128 | 129 | # 0.18.0 130 | 131 | - Upgrade all dependencies 132 | 133 | # 0.17.2 134 | 135 | - Allow removing `core-js` alias to allow for any version of `core-js`. See https://github.com/KidkArolis/jetpack/pull/69. 136 | 137 | # 0.17.1 138 | 139 | - Add `options.publicPath` - allows to specify where assets will be served from, e.g. a CDN url. 140 | 141 | # 0.17 142 | 143 | **Big improvements! 🎉** 144 | 145 | - Add support for differential bundling - jetpack can output modern and legacy bundles 146 | - Modern bundles are smaller and less transpiled compared to the previous version 147 | - Ship a complementary package for differential serving - [jetpack-serve](https://github.com/KidkArolis/jetpack-serve) 148 | - Transpile node_modules ensuring modern packages from npm work as expected 149 | - Add content hashes to output file names to improve long term caching 150 | - Add `-i, --print-config` option to dev and build commands 151 | - Upgrade all dependencies 152 | 153 | **Differential bundling** 154 | 155 | - By default, jetpack only compiles for modern browsers. 156 | - To see what browsers those are, jetpack provides a new command `jetpack browsers` that prints the browserslist query, list of browsers and coverage. 157 | - To opt into legacy browser bundling you should configure a new option `options.target = { modern: true, legacy: true }`. 158 | - Or pass `--legacy`, `--modern` or both to `serve`, `build`, `inspect` and `browsers`, e.g.: 159 | 160 | ``` 161 | $ jetpack --legacy 162 | $ jetpack inspect --legacy 163 | $ jetpack browsers --legacy 164 | $ jetpack browsers --modern 165 | $ jetpack browsers --legacy --modern 166 | ``` 167 | 168 | - Previously, jetpack would not always correctly transpile async/await. Now, jetpack ships with it's own copy of regenerator, but only uses it in legacy browsers by default. Modern browsers will get no async/await transpilation! 169 | - You can customize what browsers are considered modern and legacy using any of the methods supported by browserslist. Use `modern` and `legacy` environments to configure the browsers for each. Here's an example of `.browserslistrc` file: 170 | 171 | ``` 172 | [modern] 173 | > 10% 174 | 175 | [legacy] 176 | > 0.1% 177 | ``` 178 | 179 | - You can check that the configuration is taken into account by running `jetpack browsers` whenever you tweak your browserslist. 180 | 181 | **Differential serving** 182 | 183 | - For production serving, jetpack opted to not use module/no module approach by default due to it's 2 limitations: 184 | - First, at the moment, module/no module option in @babel/preset-env transpiles async/await into regenerator and that's not desired for modern browsers. 185 | - Second, over time, the browsers that support modules will get old, and by using browser detection to serve the right bundle we can keep transpiling less and less in the future. 186 | - By default, if you only produce a modern bundle, the output is backward compatible and can be served the same way as in previous versions of jetpack, e.g. using `express.static` middleware or by uploading `dist` to a CDN. If you produce both modern and legacy bundles, however, you will have to use the built in `jetpack/serve` module or the new [jetpack-serve(https://github.com/KidkArolis/jetpack-serve) package. Jetpack's serve middleware now detects if the browser is modern or not using the same browserslist queries used in bundling and serves the appropriate html file the `index.html` or `index.legacy.html` as appropriate. See https://github.com/KidkArolis/jetpack-serve for more details on usage. 187 | 188 | **Print config** 189 | 190 | - You can now see the config that has been generated for your dev or production builds by running some of the following: 191 | 192 | ``` 193 | jetpack -i 194 | jetpack --print-config 195 | jetpack --print-config --legacy 196 | jetpack build --print-config 197 | jetpack build --print-config --modern 198 | jetpack build --print-config --legacy 199 | ``` 200 | 201 | This prints the config using Node's `util.inspect`, and since webpack config is a JavaScript data structure that might contain functions, classes, instances and other non easily serializable things, not everyhting might be easily inspectable in this output. This is not meant to be used as copy paste into `webpack.config.js` (althought it could be a good starting point), it's mostly meant for debugging any issues and understanding exactly what jetpack is doing in your project. 202 | 203 | # 0.16.1 204 | 205 | - Run postcss over sass loader output, this fixes autoprefixing sass 206 | - Upgrade all dependencies 207 | 208 | # 0.16 209 | 210 | - Fix compiler error handling - catch build errors thoroughly and always exit with status 1 if compilation fails for any reason. 211 | - Fix all security warnings 212 | - Upgrade all dependencies, brings in file-loader@4.0.0, url-loader@2.0.0, css-loader@3.0.0 that have some breaking changes 213 | 214 | # 0.15 215 | 216 | - Add support for Sass! Simply install `node-sass` or `sass` and import '.scss' files. Works with css modules, allows specifying `resources: []` that become available to each scss file. 217 | - Fix an issue where jetpack/serve was running command line arg parsing, preventing ability to require apps that import jetpack/serve. This is useful when you try and require your app for debugging and tests. 218 | - Upgrade to core-js@3 219 | - Upgrade all dependencies 220 | 221 | # 0.14.2 222 | 223 | - Fix compiler error handling 224 | 225 | # 0.14.1 226 | 227 | - Fix compiler error handling 228 | 229 | # 0.14.0 230 | 231 | - Upgrade all deps 232 | - Fix how `babel` and `@babel/preset-env` detects module types when deciding how to inject polyfills, by using `sourceType: 'unambiguous'`, we ensure that no matter if you use CJS or ESM in your modules, jetpack will bundle them correctly and inject core-js polyfills correctly (most of the time anyway..). 233 | 234 | # 0.13.0 235 | 236 | - Upgrade all deps 237 | - Fix `react-hot-loader` webpack plugin config 238 | 239 | # 0.12.2 240 | 241 | - Fix reading `title` from config file 242 | 243 | # 0.12.1 244 | 245 | - Fix where `react-hot-loader` is loaded from, it should be loaded from the target dir 246 | 247 | # 0.12.0 248 | 249 | - Rename the `--no-hot` shorthand from `-h` to `-r`, to reclaim `-h` for help info 250 | - Fix `jetpack.config.js#hot` option, it wasn't being read from options since cli arg was always defaulting to `false` 251 | - No longer refresh the page if webpack hot patch is not accepted, for nicer user experience. It's still possible to configure page reloading with manual config override using `jetpack.config.js#webpack`. 252 | - Improve hot reloading support. `react-hot-loader` has been removed from jetpack. User's of jetpack now need to install `react-hot-loader` to opt into using it. Webpack config has been updated to work with `react-hot-loader@4.6.0` which supports React Hooks out of the box and improves the overall experience. 253 | 254 | To use this you first need to install `react-hot-loader` with `npm i -D react-hot-loader` and then update the code from: 255 | 256 | ``` 257 | import { hot } from 'jetpack/react-hot-loader' 258 | const App = () =>
Hello World!
259 | export default hot(module)(App) 260 | ``` 261 | 262 | to 263 | 264 | ``` 265 | import { hot } from 'react-hot-loader/root' 266 | const App = () =>
Hello World!
267 | export default hot(App) 268 | ``` 269 | 270 | # 0.11.0 271 | 272 | - Upgrade css-loader to 2.0.0. This upgrades it's dependency on PostCSS to v7, which means there's only one version of PostCSS being used by jetpack, which means faster install times and better performance. 273 | 274 | # 0.10.4 275 | 276 | - Fix `jetpack/serve` production mode, serve index.html if requested pathname does not exist in dist 277 | 278 | # 0.10.3 279 | 280 | - Remove console.log from the jetpack/serve module 281 | 282 | # 0.10.2 283 | 284 | - Fix `jetpack inspect` command 285 | - Fix `proxy` to properly handle node's req and res 286 | - Fix compatibility 287 | 288 | # 0.10.1 289 | 290 | - Fix `react-hot-loader` to work even when jetpack not installed locally 291 | - Fix `proxy` to work with non express servers 292 | - Only configure `react-hot-loader` if hot reloading is enabled and react is installed 293 | 294 | # 0.10.0 295 | 296 | Everything changed. Apologies if it broke your project. See the README and docs to learn about all the new features, command line args and configuration options. 297 | -------------------------------------------------------------------------------- /examples/serve-with-fastify/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serve-with-fastify", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "serve-with-fastify", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "fastify": "^5.4.0" 13 | } 14 | }, 15 | "node_modules/@fastify/ajv-compiler": { 16 | "version": "4.0.5", 17 | "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", 18 | "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", 19 | "funding": [ 20 | { 21 | "type": "github", 22 | "url": "https://github.com/sponsors/fastify" 23 | }, 24 | { 25 | "type": "opencollective", 26 | "url": "https://opencollective.com/fastify" 27 | } 28 | ], 29 | "license": "MIT", 30 | "dependencies": { 31 | "ajv": "^8.12.0", 32 | "ajv-formats": "^3.0.1", 33 | "fast-uri": "^3.0.0" 34 | } 35 | }, 36 | "node_modules/@fastify/error": { 37 | "version": "4.2.0", 38 | "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", 39 | "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", 40 | "funding": [ 41 | { 42 | "type": "github", 43 | "url": "https://github.com/sponsors/fastify" 44 | }, 45 | { 46 | "type": "opencollective", 47 | "url": "https://opencollective.com/fastify" 48 | } 49 | ], 50 | "license": "MIT" 51 | }, 52 | "node_modules/@fastify/fast-json-stringify-compiler": { 53 | "version": "5.0.3", 54 | "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", 55 | "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", 56 | "funding": [ 57 | { 58 | "type": "github", 59 | "url": "https://github.com/sponsors/fastify" 60 | }, 61 | { 62 | "type": "opencollective", 63 | "url": "https://opencollective.com/fastify" 64 | } 65 | ], 66 | "license": "MIT", 67 | "dependencies": { 68 | "fast-json-stringify": "^6.0.0" 69 | } 70 | }, 71 | "node_modules/@fastify/forwarded": { 72 | "version": "3.0.1", 73 | "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", 74 | "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", 75 | "funding": [ 76 | { 77 | "type": "github", 78 | "url": "https://github.com/sponsors/fastify" 79 | }, 80 | { 81 | "type": "opencollective", 82 | "url": "https://opencollective.com/fastify" 83 | } 84 | ], 85 | "license": "MIT" 86 | }, 87 | "node_modules/@fastify/merge-json-schemas": { 88 | "version": "0.2.1", 89 | "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", 90 | "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", 91 | "funding": [ 92 | { 93 | "type": "github", 94 | "url": "https://github.com/sponsors/fastify" 95 | }, 96 | { 97 | "type": "opencollective", 98 | "url": "https://opencollective.com/fastify" 99 | } 100 | ], 101 | "license": "MIT", 102 | "dependencies": { 103 | "dequal": "^2.0.3" 104 | } 105 | }, 106 | "node_modules/@fastify/proxy-addr": { 107 | "version": "5.1.0", 108 | "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", 109 | "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", 110 | "funding": [ 111 | { 112 | "type": "github", 113 | "url": "https://github.com/sponsors/fastify" 114 | }, 115 | { 116 | "type": "opencollective", 117 | "url": "https://opencollective.com/fastify" 118 | } 119 | ], 120 | "license": "MIT", 121 | "dependencies": { 122 | "@fastify/forwarded": "^3.0.0", 123 | "ipaddr.js": "^2.1.0" 124 | } 125 | }, 126 | "node_modules/@pinojs/redact": { 127 | "version": "0.4.0", 128 | "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", 129 | "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", 130 | "license": "MIT" 131 | }, 132 | "node_modules/abstract-logging": { 133 | "version": "2.0.1", 134 | "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", 135 | "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", 136 | "license": "MIT" 137 | }, 138 | "node_modules/ajv": { 139 | "version": "8.17.1", 140 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", 141 | "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 142 | "license": "MIT", 143 | "dependencies": { 144 | "fast-deep-equal": "^3.1.3", 145 | "fast-uri": "^3.0.1", 146 | "json-schema-traverse": "^1.0.0", 147 | "require-from-string": "^2.0.2" 148 | }, 149 | "funding": { 150 | "type": "github", 151 | "url": "https://github.com/sponsors/epoberezkin" 152 | } 153 | }, 154 | "node_modules/ajv-formats": { 155 | "version": "3.0.1", 156 | "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", 157 | "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", 158 | "license": "MIT", 159 | "dependencies": { 160 | "ajv": "^8.0.0" 161 | }, 162 | "peerDependencies": { 163 | "ajv": "^8.0.0" 164 | }, 165 | "peerDependenciesMeta": { 166 | "ajv": { 167 | "optional": true 168 | } 169 | } 170 | }, 171 | "node_modules/atomic-sleep": { 172 | "version": "1.0.0", 173 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 174 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", 175 | "license": "MIT", 176 | "engines": { 177 | "node": ">=8.0.0" 178 | } 179 | }, 180 | "node_modules/avvio": { 181 | "version": "9.1.0", 182 | "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", 183 | "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", 184 | "license": "MIT", 185 | "dependencies": { 186 | "@fastify/error": "^4.0.0", 187 | "fastq": "^1.17.1" 188 | } 189 | }, 190 | "node_modules/cookie": { 191 | "version": "1.0.2", 192 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", 193 | "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", 194 | "license": "MIT", 195 | "engines": { 196 | "node": ">=18" 197 | } 198 | }, 199 | "node_modules/dequal": { 200 | "version": "2.0.3", 201 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 202 | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 203 | "license": "MIT", 204 | "engines": { 205 | "node": ">=6" 206 | } 207 | }, 208 | "node_modules/fast-decode-uri-component": { 209 | "version": "1.0.1", 210 | "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", 211 | "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", 212 | "license": "MIT" 213 | }, 214 | "node_modules/fast-deep-equal": { 215 | "version": "3.1.3", 216 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 217 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 218 | "license": "MIT" 219 | }, 220 | "node_modules/fast-json-stringify": { 221 | "version": "6.1.1", 222 | "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.1.1.tgz", 223 | "integrity": "sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==", 224 | "funding": [ 225 | { 226 | "type": "github", 227 | "url": "https://github.com/sponsors/fastify" 228 | }, 229 | { 230 | "type": "opencollective", 231 | "url": "https://opencollective.com/fastify" 232 | } 233 | ], 234 | "license": "MIT", 235 | "dependencies": { 236 | "@fastify/merge-json-schemas": "^0.2.0", 237 | "ajv": "^8.12.0", 238 | "ajv-formats": "^3.0.1", 239 | "fast-uri": "^3.0.0", 240 | "json-schema-ref-resolver": "^3.0.0", 241 | "rfdc": "^1.2.0" 242 | } 243 | }, 244 | "node_modules/fast-querystring": { 245 | "version": "1.1.2", 246 | "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", 247 | "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", 248 | "license": "MIT", 249 | "dependencies": { 250 | "fast-decode-uri-component": "^1.0.1" 251 | } 252 | }, 253 | "node_modules/fast-uri": { 254 | "version": "3.1.0", 255 | "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", 256 | "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", 257 | "funding": [ 258 | { 259 | "type": "github", 260 | "url": "https://github.com/sponsors/fastify" 261 | }, 262 | { 263 | "type": "opencollective", 264 | "url": "https://opencollective.com/fastify" 265 | } 266 | ], 267 | "license": "BSD-3-Clause" 268 | }, 269 | "node_modules/fastify": { 270 | "version": "5.6.2", 271 | "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.2.tgz", 272 | "integrity": "sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==", 273 | "funding": [ 274 | { 275 | "type": "github", 276 | "url": "https://github.com/sponsors/fastify" 277 | }, 278 | { 279 | "type": "opencollective", 280 | "url": "https://opencollective.com/fastify" 281 | } 282 | ], 283 | "license": "MIT", 284 | "dependencies": { 285 | "@fastify/ajv-compiler": "^4.0.0", 286 | "@fastify/error": "^4.0.0", 287 | "@fastify/fast-json-stringify-compiler": "^5.0.0", 288 | "@fastify/proxy-addr": "^5.0.0", 289 | "abstract-logging": "^2.0.1", 290 | "avvio": "^9.0.0", 291 | "fast-json-stringify": "^6.0.0", 292 | "find-my-way": "^9.0.0", 293 | "light-my-request": "^6.0.0", 294 | "pino": "^10.1.0", 295 | "process-warning": "^5.0.0", 296 | "rfdc": "^1.3.1", 297 | "secure-json-parse": "^4.0.0", 298 | "semver": "^7.6.0", 299 | "toad-cache": "^3.7.0" 300 | } 301 | }, 302 | "node_modules/fastq": { 303 | "version": "1.19.1", 304 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 305 | "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 306 | "license": "ISC", 307 | "dependencies": { 308 | "reusify": "^1.0.4" 309 | } 310 | }, 311 | "node_modules/find-my-way": { 312 | "version": "9.3.0", 313 | "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", 314 | "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", 315 | "license": "MIT", 316 | "dependencies": { 317 | "fast-deep-equal": "^3.1.3", 318 | "fast-querystring": "^1.0.0", 319 | "safe-regex2": "^5.0.0" 320 | }, 321 | "engines": { 322 | "node": ">=20" 323 | } 324 | }, 325 | "node_modules/ipaddr.js": { 326 | "version": "2.2.0", 327 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", 328 | "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", 329 | "license": "MIT", 330 | "engines": { 331 | "node": ">= 10" 332 | } 333 | }, 334 | "node_modules/json-schema-ref-resolver": { 335 | "version": "3.0.0", 336 | "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", 337 | "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", 338 | "funding": [ 339 | { 340 | "type": "github", 341 | "url": "https://github.com/sponsors/fastify" 342 | }, 343 | { 344 | "type": "opencollective", 345 | "url": "https://opencollective.com/fastify" 346 | } 347 | ], 348 | "license": "MIT", 349 | "dependencies": { 350 | "dequal": "^2.0.3" 351 | } 352 | }, 353 | "node_modules/json-schema-traverse": { 354 | "version": "1.0.0", 355 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 356 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 357 | "license": "MIT" 358 | }, 359 | "node_modules/light-my-request": { 360 | "version": "6.6.0", 361 | "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", 362 | "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", 363 | "funding": [ 364 | { 365 | "type": "github", 366 | "url": "https://github.com/sponsors/fastify" 367 | }, 368 | { 369 | "type": "opencollective", 370 | "url": "https://opencollective.com/fastify" 371 | } 372 | ], 373 | "license": "BSD-3-Clause", 374 | "dependencies": { 375 | "cookie": "^1.0.1", 376 | "process-warning": "^4.0.0", 377 | "set-cookie-parser": "^2.6.0" 378 | } 379 | }, 380 | "node_modules/light-my-request/node_modules/process-warning": { 381 | "version": "4.0.1", 382 | "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", 383 | "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", 384 | "funding": [ 385 | { 386 | "type": "github", 387 | "url": "https://github.com/sponsors/fastify" 388 | }, 389 | { 390 | "type": "opencollective", 391 | "url": "https://opencollective.com/fastify" 392 | } 393 | ], 394 | "license": "MIT" 395 | }, 396 | "node_modules/on-exit-leak-free": { 397 | "version": "2.1.2", 398 | "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", 399 | "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", 400 | "license": "MIT", 401 | "engines": { 402 | "node": ">=14.0.0" 403 | } 404 | }, 405 | "node_modules/pino": { 406 | "version": "10.1.0", 407 | "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", 408 | "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", 409 | "license": "MIT", 410 | "dependencies": { 411 | "@pinojs/redact": "^0.4.0", 412 | "atomic-sleep": "^1.0.0", 413 | "on-exit-leak-free": "^2.1.0", 414 | "pino-abstract-transport": "^2.0.0", 415 | "pino-std-serializers": "^7.0.0", 416 | "process-warning": "^5.0.0", 417 | "quick-format-unescaped": "^4.0.3", 418 | "real-require": "^0.2.0", 419 | "safe-stable-stringify": "^2.3.1", 420 | "sonic-boom": "^4.0.1", 421 | "thread-stream": "^3.0.0" 422 | }, 423 | "bin": { 424 | "pino": "bin.js" 425 | } 426 | }, 427 | "node_modules/pino-abstract-transport": { 428 | "version": "2.0.0", 429 | "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", 430 | "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", 431 | "license": "MIT", 432 | "dependencies": { 433 | "split2": "^4.0.0" 434 | } 435 | }, 436 | "node_modules/pino-std-serializers": { 437 | "version": "7.0.0", 438 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", 439 | "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", 440 | "license": "MIT" 441 | }, 442 | "node_modules/process-warning": { 443 | "version": "5.0.0", 444 | "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", 445 | "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", 446 | "funding": [ 447 | { 448 | "type": "github", 449 | "url": "https://github.com/sponsors/fastify" 450 | }, 451 | { 452 | "type": "opencollective", 453 | "url": "https://opencollective.com/fastify" 454 | } 455 | ], 456 | "license": "MIT" 457 | }, 458 | "node_modules/quick-format-unescaped": { 459 | "version": "4.0.4", 460 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", 461 | "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", 462 | "license": "MIT" 463 | }, 464 | "node_modules/real-require": { 465 | "version": "0.2.0", 466 | "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", 467 | "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", 468 | "license": "MIT", 469 | "engines": { 470 | "node": ">= 12.13.0" 471 | } 472 | }, 473 | "node_modules/require-from-string": { 474 | "version": "2.0.2", 475 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 476 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 477 | "license": "MIT", 478 | "engines": { 479 | "node": ">=0.10.0" 480 | } 481 | }, 482 | "node_modules/ret": { 483 | "version": "0.5.0", 484 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", 485 | "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", 486 | "license": "MIT", 487 | "engines": { 488 | "node": ">=10" 489 | } 490 | }, 491 | "node_modules/reusify": { 492 | "version": "1.1.0", 493 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 494 | "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 495 | "license": "MIT", 496 | "engines": { 497 | "iojs": ">=1.0.0", 498 | "node": ">=0.10.0" 499 | } 500 | }, 501 | "node_modules/rfdc": { 502 | "version": "1.4.1", 503 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", 504 | "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", 505 | "license": "MIT" 506 | }, 507 | "node_modules/safe-regex2": { 508 | "version": "5.0.0", 509 | "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", 510 | "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", 511 | "funding": [ 512 | { 513 | "type": "github", 514 | "url": "https://github.com/sponsors/fastify" 515 | }, 516 | { 517 | "type": "opencollective", 518 | "url": "https://opencollective.com/fastify" 519 | } 520 | ], 521 | "license": "MIT", 522 | "dependencies": { 523 | "ret": "~0.5.0" 524 | } 525 | }, 526 | "node_modules/safe-stable-stringify": { 527 | "version": "2.5.0", 528 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", 529 | "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", 530 | "license": "MIT", 531 | "engines": { 532 | "node": ">=10" 533 | } 534 | }, 535 | "node_modules/secure-json-parse": { 536 | "version": "4.1.0", 537 | "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", 538 | "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", 539 | "funding": [ 540 | { 541 | "type": "github", 542 | "url": "https://github.com/sponsors/fastify" 543 | }, 544 | { 545 | "type": "opencollective", 546 | "url": "https://opencollective.com/fastify" 547 | } 548 | ], 549 | "license": "BSD-3-Clause" 550 | }, 551 | "node_modules/semver": { 552 | "version": "7.7.3", 553 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 554 | "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 555 | "license": "ISC", 556 | "bin": { 557 | "semver": "bin/semver.js" 558 | }, 559 | "engines": { 560 | "node": ">=10" 561 | } 562 | }, 563 | "node_modules/set-cookie-parser": { 564 | "version": "2.7.2", 565 | "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", 566 | "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", 567 | "license": "MIT" 568 | }, 569 | "node_modules/sonic-boom": { 570 | "version": "4.2.0", 571 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", 572 | "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", 573 | "license": "MIT", 574 | "dependencies": { 575 | "atomic-sleep": "^1.0.0" 576 | } 577 | }, 578 | "node_modules/split2": { 579 | "version": "4.2.0", 580 | "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", 581 | "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", 582 | "license": "ISC", 583 | "engines": { 584 | "node": ">= 10.x" 585 | } 586 | }, 587 | "node_modules/thread-stream": { 588 | "version": "3.1.0", 589 | "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", 590 | "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", 591 | "license": "MIT", 592 | "dependencies": { 593 | "real-require": "^0.2.0" 594 | } 595 | }, 596 | "node_modules/toad-cache": { 597 | "version": "3.7.0", 598 | "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", 599 | "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", 600 | "license": "MIT", 601 | "engines": { 602 | "node": ">=12" 603 | } 604 | } 605 | } 606 | } 607 | -------------------------------------------------------------------------------- /examples/serve-with-express/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serve-with-express", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "serve-with-express", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "express": "^5.1.0" 13 | } 14 | }, 15 | "node_modules/accepts": { 16 | "version": "2.0.0", 17 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 18 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 19 | "license": "MIT", 20 | "dependencies": { 21 | "mime-types": "^3.0.0", 22 | "negotiator": "^1.0.0" 23 | }, 24 | "engines": { 25 | "node": ">= 0.6" 26 | } 27 | }, 28 | "node_modules/body-parser": { 29 | "version": "2.2.0", 30 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 31 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "bytes": "^3.1.2", 35 | "content-type": "^1.0.5", 36 | "debug": "^4.4.0", 37 | "http-errors": "^2.0.0", 38 | "iconv-lite": "^0.6.3", 39 | "on-finished": "^2.4.1", 40 | "qs": "^6.14.0", 41 | "raw-body": "^3.0.0", 42 | "type-is": "^2.0.0" 43 | }, 44 | "engines": { 45 | "node": ">=18" 46 | } 47 | }, 48 | "node_modules/bytes": { 49 | "version": "3.1.2", 50 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 51 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 52 | "license": "MIT", 53 | "engines": { 54 | "node": ">= 0.8" 55 | } 56 | }, 57 | "node_modules/call-bind-apply-helpers": { 58 | "version": "1.0.2", 59 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 60 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 61 | "license": "MIT", 62 | "dependencies": { 63 | "es-errors": "^1.3.0", 64 | "function-bind": "^1.1.2" 65 | }, 66 | "engines": { 67 | "node": ">= 0.4" 68 | } 69 | }, 70 | "node_modules/call-bound": { 71 | "version": "1.0.4", 72 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 73 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 74 | "license": "MIT", 75 | "dependencies": { 76 | "call-bind-apply-helpers": "^1.0.2", 77 | "get-intrinsic": "^1.3.0" 78 | }, 79 | "engines": { 80 | "node": ">= 0.4" 81 | }, 82 | "funding": { 83 | "url": "https://github.com/sponsors/ljharb" 84 | } 85 | }, 86 | "node_modules/content-disposition": { 87 | "version": "1.0.1", 88 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", 89 | "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", 90 | "license": "MIT", 91 | "engines": { 92 | "node": ">=18" 93 | }, 94 | "funding": { 95 | "type": "opencollective", 96 | "url": "https://opencollective.com/express" 97 | } 98 | }, 99 | "node_modules/content-type": { 100 | "version": "1.0.5", 101 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 102 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 103 | "license": "MIT", 104 | "engines": { 105 | "node": ">= 0.6" 106 | } 107 | }, 108 | "node_modules/cookie": { 109 | "version": "0.7.2", 110 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 111 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 112 | "license": "MIT", 113 | "engines": { 114 | "node": ">= 0.6" 115 | } 116 | }, 117 | "node_modules/cookie-signature": { 118 | "version": "1.2.2", 119 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 120 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 121 | "license": "MIT", 122 | "engines": { 123 | "node": ">=6.6.0" 124 | } 125 | }, 126 | "node_modules/debug": { 127 | "version": "4.4.3", 128 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 129 | "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 130 | "license": "MIT", 131 | "dependencies": { 132 | "ms": "^2.1.3" 133 | }, 134 | "engines": { 135 | "node": ">=6.0" 136 | }, 137 | "peerDependenciesMeta": { 138 | "supports-color": { 139 | "optional": true 140 | } 141 | } 142 | }, 143 | "node_modules/depd": { 144 | "version": "2.0.0", 145 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 146 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 147 | "license": "MIT", 148 | "engines": { 149 | "node": ">= 0.8" 150 | } 151 | }, 152 | "node_modules/dunder-proto": { 153 | "version": "1.0.1", 154 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 155 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 156 | "license": "MIT", 157 | "dependencies": { 158 | "call-bind-apply-helpers": "^1.0.1", 159 | "es-errors": "^1.3.0", 160 | "gopd": "^1.2.0" 161 | }, 162 | "engines": { 163 | "node": ">= 0.4" 164 | } 165 | }, 166 | "node_modules/ee-first": { 167 | "version": "1.1.1", 168 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 169 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 170 | "license": "MIT" 171 | }, 172 | "node_modules/encodeurl": { 173 | "version": "2.0.0", 174 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 175 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 176 | "license": "MIT", 177 | "engines": { 178 | "node": ">= 0.8" 179 | } 180 | }, 181 | "node_modules/es-define-property": { 182 | "version": "1.0.1", 183 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 184 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 185 | "license": "MIT", 186 | "engines": { 187 | "node": ">= 0.4" 188 | } 189 | }, 190 | "node_modules/es-errors": { 191 | "version": "1.3.0", 192 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 193 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 194 | "license": "MIT", 195 | "engines": { 196 | "node": ">= 0.4" 197 | } 198 | }, 199 | "node_modules/es-object-atoms": { 200 | "version": "1.1.1", 201 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 202 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 203 | "license": "MIT", 204 | "dependencies": { 205 | "es-errors": "^1.3.0" 206 | }, 207 | "engines": { 208 | "node": ">= 0.4" 209 | } 210 | }, 211 | "node_modules/escape-html": { 212 | "version": "1.0.3", 213 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 214 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 215 | "license": "MIT" 216 | }, 217 | "node_modules/etag": { 218 | "version": "1.8.1", 219 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 220 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 221 | "license": "MIT", 222 | "engines": { 223 | "node": ">= 0.6" 224 | } 225 | }, 226 | "node_modules/express": { 227 | "version": "5.1.0", 228 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 229 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 230 | "license": "MIT", 231 | "dependencies": { 232 | "accepts": "^2.0.0", 233 | "body-parser": "^2.2.0", 234 | "content-disposition": "^1.0.0", 235 | "content-type": "^1.0.5", 236 | "cookie": "^0.7.1", 237 | "cookie-signature": "^1.2.1", 238 | "debug": "^4.4.0", 239 | "encodeurl": "^2.0.0", 240 | "escape-html": "^1.0.3", 241 | "etag": "^1.8.1", 242 | "finalhandler": "^2.1.0", 243 | "fresh": "^2.0.0", 244 | "http-errors": "^2.0.0", 245 | "merge-descriptors": "^2.0.0", 246 | "mime-types": "^3.0.0", 247 | "on-finished": "^2.4.1", 248 | "once": "^1.4.0", 249 | "parseurl": "^1.3.3", 250 | "proxy-addr": "^2.0.7", 251 | "qs": "^6.14.0", 252 | "range-parser": "^1.2.1", 253 | "router": "^2.2.0", 254 | "send": "^1.1.0", 255 | "serve-static": "^2.2.0", 256 | "statuses": "^2.0.1", 257 | "type-is": "^2.0.1", 258 | "vary": "^1.1.2" 259 | }, 260 | "engines": { 261 | "node": ">= 18" 262 | }, 263 | "funding": { 264 | "type": "opencollective", 265 | "url": "https://opencollective.com/express" 266 | } 267 | }, 268 | "node_modules/finalhandler": { 269 | "version": "2.1.0", 270 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 271 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 272 | "license": "MIT", 273 | "dependencies": { 274 | "debug": "^4.4.0", 275 | "encodeurl": "^2.0.0", 276 | "escape-html": "^1.0.3", 277 | "on-finished": "^2.4.1", 278 | "parseurl": "^1.3.3", 279 | "statuses": "^2.0.1" 280 | }, 281 | "engines": { 282 | "node": ">= 0.8" 283 | } 284 | }, 285 | "node_modules/forwarded": { 286 | "version": "0.2.0", 287 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 288 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 289 | "license": "MIT", 290 | "engines": { 291 | "node": ">= 0.6" 292 | } 293 | }, 294 | "node_modules/fresh": { 295 | "version": "2.0.0", 296 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 297 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 298 | "license": "MIT", 299 | "engines": { 300 | "node": ">= 0.8" 301 | } 302 | }, 303 | "node_modules/function-bind": { 304 | "version": "1.1.2", 305 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 306 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 307 | "license": "MIT", 308 | "funding": { 309 | "url": "https://github.com/sponsors/ljharb" 310 | } 311 | }, 312 | "node_modules/get-intrinsic": { 313 | "version": "1.3.0", 314 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 315 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 316 | "license": "MIT", 317 | "dependencies": { 318 | "call-bind-apply-helpers": "^1.0.2", 319 | "es-define-property": "^1.0.1", 320 | "es-errors": "^1.3.0", 321 | "es-object-atoms": "^1.1.1", 322 | "function-bind": "^1.1.2", 323 | "get-proto": "^1.0.1", 324 | "gopd": "^1.2.0", 325 | "has-symbols": "^1.1.0", 326 | "hasown": "^2.0.2", 327 | "math-intrinsics": "^1.1.0" 328 | }, 329 | "engines": { 330 | "node": ">= 0.4" 331 | }, 332 | "funding": { 333 | "url": "https://github.com/sponsors/ljharb" 334 | } 335 | }, 336 | "node_modules/get-proto": { 337 | "version": "1.0.1", 338 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 339 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 340 | "license": "MIT", 341 | "dependencies": { 342 | "dunder-proto": "^1.0.1", 343 | "es-object-atoms": "^1.0.0" 344 | }, 345 | "engines": { 346 | "node": ">= 0.4" 347 | } 348 | }, 349 | "node_modules/gopd": { 350 | "version": "1.2.0", 351 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 352 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 353 | "license": "MIT", 354 | "engines": { 355 | "node": ">= 0.4" 356 | }, 357 | "funding": { 358 | "url": "https://github.com/sponsors/ljharb" 359 | } 360 | }, 361 | "node_modules/has-symbols": { 362 | "version": "1.1.0", 363 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 364 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 365 | "license": "MIT", 366 | "engines": { 367 | "node": ">= 0.4" 368 | }, 369 | "funding": { 370 | "url": "https://github.com/sponsors/ljharb" 371 | } 372 | }, 373 | "node_modules/hasown": { 374 | "version": "2.0.2", 375 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 376 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 377 | "license": "MIT", 378 | "dependencies": { 379 | "function-bind": "^1.1.2" 380 | }, 381 | "engines": { 382 | "node": ">= 0.4" 383 | } 384 | }, 385 | "node_modules/http-errors": { 386 | "version": "2.0.1", 387 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", 388 | "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", 389 | "license": "MIT", 390 | "dependencies": { 391 | "depd": "~2.0.0", 392 | "inherits": "~2.0.4", 393 | "setprototypeof": "~1.2.0", 394 | "statuses": "~2.0.2", 395 | "toidentifier": "~1.0.1" 396 | }, 397 | "engines": { 398 | "node": ">= 0.8" 399 | }, 400 | "funding": { 401 | "type": "opencollective", 402 | "url": "https://opencollective.com/express" 403 | } 404 | }, 405 | "node_modules/iconv-lite": { 406 | "version": "0.6.3", 407 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 408 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 409 | "license": "MIT", 410 | "dependencies": { 411 | "safer-buffer": ">= 2.1.2 < 3.0.0" 412 | }, 413 | "engines": { 414 | "node": ">=0.10.0" 415 | } 416 | }, 417 | "node_modules/inherits": { 418 | "version": "2.0.4", 419 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 420 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 421 | "license": "ISC" 422 | }, 423 | "node_modules/ipaddr.js": { 424 | "version": "1.9.1", 425 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 426 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 427 | "license": "MIT", 428 | "engines": { 429 | "node": ">= 0.10" 430 | } 431 | }, 432 | "node_modules/is-promise": { 433 | "version": "4.0.0", 434 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 435 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 436 | "license": "MIT" 437 | }, 438 | "node_modules/math-intrinsics": { 439 | "version": "1.1.0", 440 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 441 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 442 | "license": "MIT", 443 | "engines": { 444 | "node": ">= 0.4" 445 | } 446 | }, 447 | "node_modules/media-typer": { 448 | "version": "1.1.0", 449 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 450 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 451 | "license": "MIT", 452 | "engines": { 453 | "node": ">= 0.8" 454 | } 455 | }, 456 | "node_modules/merge-descriptors": { 457 | "version": "2.0.0", 458 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 459 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 460 | "license": "MIT", 461 | "engines": { 462 | "node": ">=18" 463 | }, 464 | "funding": { 465 | "url": "https://github.com/sponsors/sindresorhus" 466 | } 467 | }, 468 | "node_modules/mime-db": { 469 | "version": "1.54.0", 470 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 471 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 472 | "license": "MIT", 473 | "engines": { 474 | "node": ">= 0.6" 475 | } 476 | }, 477 | "node_modules/mime-types": { 478 | "version": "3.0.2", 479 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", 480 | "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", 481 | "license": "MIT", 482 | "dependencies": { 483 | "mime-db": "^1.54.0" 484 | }, 485 | "engines": { 486 | "node": ">=18" 487 | }, 488 | "funding": { 489 | "type": "opencollective", 490 | "url": "https://opencollective.com/express" 491 | } 492 | }, 493 | "node_modules/ms": { 494 | "version": "2.1.3", 495 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 496 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 497 | "license": "MIT" 498 | }, 499 | "node_modules/negotiator": { 500 | "version": "1.0.0", 501 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 502 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 503 | "license": "MIT", 504 | "engines": { 505 | "node": ">= 0.6" 506 | } 507 | }, 508 | "node_modules/object-inspect": { 509 | "version": "1.13.4", 510 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 511 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 512 | "license": "MIT", 513 | "engines": { 514 | "node": ">= 0.4" 515 | }, 516 | "funding": { 517 | "url": "https://github.com/sponsors/ljharb" 518 | } 519 | }, 520 | "node_modules/on-finished": { 521 | "version": "2.4.1", 522 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 523 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 524 | "license": "MIT", 525 | "dependencies": { 526 | "ee-first": "1.1.1" 527 | }, 528 | "engines": { 529 | "node": ">= 0.8" 530 | } 531 | }, 532 | "node_modules/once": { 533 | "version": "1.4.0", 534 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 535 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 536 | "license": "ISC", 537 | "dependencies": { 538 | "wrappy": "1" 539 | } 540 | }, 541 | "node_modules/parseurl": { 542 | "version": "1.3.3", 543 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 544 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 545 | "license": "MIT", 546 | "engines": { 547 | "node": ">= 0.8" 548 | } 549 | }, 550 | "node_modules/path-to-regexp": { 551 | "version": "8.3.0", 552 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", 553 | "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", 554 | "license": "MIT", 555 | "funding": { 556 | "type": "opencollective", 557 | "url": "https://opencollective.com/express" 558 | } 559 | }, 560 | "node_modules/proxy-addr": { 561 | "version": "2.0.7", 562 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 563 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 564 | "license": "MIT", 565 | "dependencies": { 566 | "forwarded": "0.2.0", 567 | "ipaddr.js": "1.9.1" 568 | }, 569 | "engines": { 570 | "node": ">= 0.10" 571 | } 572 | }, 573 | "node_modules/qs": { 574 | "version": "6.14.0", 575 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 576 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 577 | "license": "BSD-3-Clause", 578 | "dependencies": { 579 | "side-channel": "^1.1.0" 580 | }, 581 | "engines": { 582 | "node": ">=0.6" 583 | }, 584 | "funding": { 585 | "url": "https://github.com/sponsors/ljharb" 586 | } 587 | }, 588 | "node_modules/range-parser": { 589 | "version": "1.2.1", 590 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 591 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 592 | "license": "MIT", 593 | "engines": { 594 | "node": ">= 0.6" 595 | } 596 | }, 597 | "node_modules/raw-body": { 598 | "version": "3.0.2", 599 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", 600 | "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", 601 | "license": "MIT", 602 | "dependencies": { 603 | "bytes": "~3.1.2", 604 | "http-errors": "~2.0.1", 605 | "iconv-lite": "~0.7.0", 606 | "unpipe": "~1.0.0" 607 | }, 608 | "engines": { 609 | "node": ">= 0.10" 610 | } 611 | }, 612 | "node_modules/raw-body/node_modules/iconv-lite": { 613 | "version": "0.7.0", 614 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", 615 | "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", 616 | "license": "MIT", 617 | "dependencies": { 618 | "safer-buffer": ">= 2.1.2 < 3.0.0" 619 | }, 620 | "engines": { 621 | "node": ">=0.10.0" 622 | }, 623 | "funding": { 624 | "type": "opencollective", 625 | "url": "https://opencollective.com/express" 626 | } 627 | }, 628 | "node_modules/router": { 629 | "version": "2.2.0", 630 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 631 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 632 | "license": "MIT", 633 | "dependencies": { 634 | "debug": "^4.4.0", 635 | "depd": "^2.0.0", 636 | "is-promise": "^4.0.0", 637 | "parseurl": "^1.3.3", 638 | "path-to-regexp": "^8.0.0" 639 | }, 640 | "engines": { 641 | "node": ">= 18" 642 | } 643 | }, 644 | "node_modules/safer-buffer": { 645 | "version": "2.1.2", 646 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 647 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 648 | "license": "MIT" 649 | }, 650 | "node_modules/send": { 651 | "version": "1.2.0", 652 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 653 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 654 | "license": "MIT", 655 | "dependencies": { 656 | "debug": "^4.3.5", 657 | "encodeurl": "^2.0.0", 658 | "escape-html": "^1.0.3", 659 | "etag": "^1.8.1", 660 | "fresh": "^2.0.0", 661 | "http-errors": "^2.0.0", 662 | "mime-types": "^3.0.1", 663 | "ms": "^2.1.3", 664 | "on-finished": "^2.4.1", 665 | "range-parser": "^1.2.1", 666 | "statuses": "^2.0.1" 667 | }, 668 | "engines": { 669 | "node": ">= 18" 670 | } 671 | }, 672 | "node_modules/serve-static": { 673 | "version": "2.2.0", 674 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 675 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 676 | "license": "MIT", 677 | "dependencies": { 678 | "encodeurl": "^2.0.0", 679 | "escape-html": "^1.0.3", 680 | "parseurl": "^1.3.3", 681 | "send": "^1.2.0" 682 | }, 683 | "engines": { 684 | "node": ">= 18" 685 | } 686 | }, 687 | "node_modules/setprototypeof": { 688 | "version": "1.2.0", 689 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 690 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 691 | "license": "ISC" 692 | }, 693 | "node_modules/side-channel": { 694 | "version": "1.1.0", 695 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 696 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 697 | "license": "MIT", 698 | "dependencies": { 699 | "es-errors": "^1.3.0", 700 | "object-inspect": "^1.13.3", 701 | "side-channel-list": "^1.0.0", 702 | "side-channel-map": "^1.0.1", 703 | "side-channel-weakmap": "^1.0.2" 704 | }, 705 | "engines": { 706 | "node": ">= 0.4" 707 | }, 708 | "funding": { 709 | "url": "https://github.com/sponsors/ljharb" 710 | } 711 | }, 712 | "node_modules/side-channel-list": { 713 | "version": "1.0.0", 714 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 715 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 716 | "license": "MIT", 717 | "dependencies": { 718 | "es-errors": "^1.3.0", 719 | "object-inspect": "^1.13.3" 720 | }, 721 | "engines": { 722 | "node": ">= 0.4" 723 | }, 724 | "funding": { 725 | "url": "https://github.com/sponsors/ljharb" 726 | } 727 | }, 728 | "node_modules/side-channel-map": { 729 | "version": "1.0.1", 730 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 731 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 732 | "license": "MIT", 733 | "dependencies": { 734 | "call-bound": "^1.0.2", 735 | "es-errors": "^1.3.0", 736 | "get-intrinsic": "^1.2.5", 737 | "object-inspect": "^1.13.3" 738 | }, 739 | "engines": { 740 | "node": ">= 0.4" 741 | }, 742 | "funding": { 743 | "url": "https://github.com/sponsors/ljharb" 744 | } 745 | }, 746 | "node_modules/side-channel-weakmap": { 747 | "version": "1.0.2", 748 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 749 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 750 | "license": "MIT", 751 | "dependencies": { 752 | "call-bound": "^1.0.2", 753 | "es-errors": "^1.3.0", 754 | "get-intrinsic": "^1.2.5", 755 | "object-inspect": "^1.13.3", 756 | "side-channel-map": "^1.0.1" 757 | }, 758 | "engines": { 759 | "node": ">= 0.4" 760 | }, 761 | "funding": { 762 | "url": "https://github.com/sponsors/ljharb" 763 | } 764 | }, 765 | "node_modules/statuses": { 766 | "version": "2.0.2", 767 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", 768 | "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", 769 | "license": "MIT", 770 | "engines": { 771 | "node": ">= 0.8" 772 | } 773 | }, 774 | "node_modules/toidentifier": { 775 | "version": "1.0.1", 776 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 777 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 778 | "license": "MIT", 779 | "engines": { 780 | "node": ">=0.6" 781 | } 782 | }, 783 | "node_modules/type-is": { 784 | "version": "2.0.1", 785 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 786 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 787 | "license": "MIT", 788 | "dependencies": { 789 | "content-type": "^1.0.5", 790 | "media-typer": "^1.1.0", 791 | "mime-types": "^3.0.0" 792 | }, 793 | "engines": { 794 | "node": ">= 0.6" 795 | } 796 | }, 797 | "node_modules/unpipe": { 798 | "version": "1.0.0", 799 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 800 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 801 | "license": "MIT", 802 | "engines": { 803 | "node": ">= 0.8" 804 | } 805 | }, 806 | "node_modules/vary": { 807 | "version": "1.1.2", 808 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 809 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 810 | "license": "MIT", 811 | "engines": { 812 | "node": ">= 0.8" 813 | } 814 | }, 815 | "node_modules/wrappy": { 816 | "version": "1.0.2", 817 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 818 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 819 | "license": "ISC" 820 | } 821 | } 822 | } 823 | --------------------------------------------------------------------------------