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 = () =>