diff --git a/.eslintignore b/.eslintignore index 01d5ff711c7..4f9cf13a89f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,6 +11,7 @@ packages/*/*/test/mochareporters.json packages/core/integration-tests/test/input/** packages/core/utils/test/input/** packages/utils/create-react-app/templates +packages/utils/create-parcel-app/templates packages/examples # Generated by the build diff --git a/gulpfile.js b/gulpfile.js index dd6ba06c6ef..9261be380b3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -32,6 +32,7 @@ const paths = { packageJson: [ 'packages/core/parcel/package.json', 'packages/utils/create-react-app/package.json', + 'packages/utils/create-parcel/package.json', 'packages/dev/query/package.json', 'packages/dev/bundle-stats-cli/package.json', ], diff --git a/packages/core/integration-tests/package.json b/packages/core/integration-tests/package.json index 6e1b53e0c1c..072187ec2fb 100644 --- a/packages/core/integration-tests/package.json +++ b/packages/core/integration-tests/package.json @@ -20,7 +20,8 @@ "@babel/preset-env": "^7.22.14", "@babel/preset-typescript": "^7.22.11", "@mdx-js/react": "^1.5.3", - "@types/react": "^17", + "@types/react": "^19", + "@types/react-dom": "^19", "autoprefixer": "^10.4.0", "chalk": "^4.1.2", "command-exists": "^1.2.6", diff --git a/packages/packagers/react-static/src/ReactStaticPackager.js b/packages/packagers/react-static/src/ReactStaticPackager.js index 2af29e1dae9..94ae32ded94 100644 --- a/packages/packagers/react-static/src/ReactStaticPackager.js +++ b/packages/packagers/react-static/src/ReactStaticPackager.js @@ -264,7 +264,6 @@ async function loadBundleUncached( ]; }); } else if (entryBundle) { - // console.log('here', entryBundle) queue.add(async () => { let {assets: subAssets} = await loadBundle( entryBundle, diff --git a/packages/reporters/dev-server/src/NodeRunner.js b/packages/reporters/dev-server/src/NodeRunner.js index 4f8c961a5b2..11d3180474f 100644 --- a/packages/reporters/dev-server/src/NodeRunner.js +++ b/packages/reporters/dev-server/src/NodeRunner.js @@ -5,6 +5,7 @@ import {md, errorToDiagnostic} from '@parcel/diagnostic'; import nullthrows from 'nullthrows'; import {Worker} from 'worker_threads'; import path from 'path'; +import {type Deferred, makeDeferredWithPromise} from '@parcel/utils'; import type {HMRMessage} from './HMRServer'; export type NodeRunnerOptions = {| @@ -15,7 +16,8 @@ export type NodeRunnerOptions = {| export class NodeRunner { worker: Worker | null = null; bundleGraph: BundleGraph | null = null; - pending: boolean = true; + pending: Promise | null = null; + deferred: Deferred | null = null; logger: PluginLogger; hmr: boolean; @@ -25,17 +27,25 @@ export class NodeRunner { } buildStart() { - this.pending = true; + let {deferred, promise} = makeDeferredWithPromise(); + this.pending = promise; + this.deferred = deferred; } - buildSuccess(bundleGraph: BundleGraph) { + async buildSuccess(bundleGraph: BundleGraph) { this.bundleGraph = bundleGraph; - this.pending = false; + + let deferred = this.deferred; + this.pending = null; + this.deferred = null; + if (this.worker == null) { - this.startWorker(); + await this.startWorker(); } else if (!this.hmr) { - this.restartWorker(); + await this.restartWorker(); } + + deferred?.resolve(); } startWorker(): Promise { @@ -88,7 +98,11 @@ export class NodeRunner { this.worker = worker; return new Promise(resolve => { - worker.once('online', () => resolve()); + if (this.hmr) { + worker.once('message', () => resolve()); + } else { + worker.once('online', () => resolve()); + } }); } else { return Promise.resolve(); @@ -107,6 +121,8 @@ export class NodeRunner { // If the build is still pending, wait until it completes to restart. if (!this.pending) { await this.startWorker(); + } else { + await this.pending; } } diff --git a/packages/reporters/dev-server/src/ServerReporter.js b/packages/reporters/dev-server/src/ServerReporter.js index 6166acb288e..8ea5be0d150 100644 --- a/packages/reporters/dev-server/src/ServerReporter.js +++ b/packages/reporters/dev-server/src/ServerReporter.js @@ -121,9 +121,13 @@ export default (new Reporter({ // If running in node, wait for the server to update before emitting the update // on the client. This ensures that when the client reloads the server is ready. if (nodeRunner) { - await nodeRunner.emitUpdate(update); + // Don't await here because that blocks the build from continuing + // and we may need to wait for the buildSuccess event. + let hmr = hmrServer; + nodeRunner.emitUpdate(update).then(() => hmr.broadcast(update)); + } else { + hmrServer.broadcast(update); } - hmrServer.broadcast(update); } } break; diff --git a/packages/runtimes/hmr/src/loaders/hmr-runtime.js b/packages/runtimes/hmr/src/loaders/hmr-runtime.js index 35d57521303..89a42998775 100644 --- a/packages/runtimes/hmr/src/loaders/hmr-runtime.js +++ b/packages/runtimes/hmr/src/loaders/hmr-runtime.js @@ -153,6 +153,9 @@ if (!parent || !parent.isParcelRequire) { parentPort.postMessage('restart'); } }); + + // After the bundle has finished running, notify the dev server that the HMR update is complete. + queueMicrotask(() => parentPort.postMessage('ready')); } } catch { if (typeof WebSocket !== 'undefined') { diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js index c449493385f..7a0aba01790 100644 --- a/packages/transformers/js/src/JSTransformer.js +++ b/packages/transformers/js/src/JSTransformer.js @@ -404,12 +404,10 @@ export default (new Transformer({ let supportsModuleWorkers = asset.env.shouldScopeHoist && asset.env.supports('worker-module', true); let isJSX = Boolean(config?.isJSX); - if (asset.isSource) { - if (asset.type === 'ts') { - isJSX = false; - } else if (!isJSX) { - isJSX = Boolean(JSX_EXTENSIONS[asset.type]); - } + if (asset.type === 'ts') { + isJSX = false; + } else if (!isJSX) { + isJSX = Boolean(JSX_EXTENSIONS[asset.type]); } let type = 'js'; diff --git a/packages/utils/create-parcel/package.json b/packages/utils/create-parcel/package.json new file mode 100644 index 00000000000..7f898c28740 --- /dev/null +++ b/packages/utils/create-parcel/package.json @@ -0,0 +1,23 @@ +{ + "name": "create-parcel", + "version": "2.13.3", + "bin": { + "create-parcel": "lib/create-parcel.js" + }, + "main": "src/create-parcel.js", + "repository": { + "type": "git", + "url": "https://github.com/parcel-bundler/parcel.git", + "directory": "packages/utils/create-parcel" + }, + "source": "src/create-parcel.js", + "files": [ + "templates", + "lib" + ], + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "dependencies": {} +} diff --git a/packages/utils/create-parcel/src/create-parcel.js b/packages/utils/create-parcel/src/create-parcel.js new file mode 100755 index 00000000000..03e159797c1 --- /dev/null +++ b/packages/utils/create-parcel/src/create-parcel.js @@ -0,0 +1,190 @@ +#!/usr/bin/env node + +// @flow +/* eslint-disable no-console */ + +// $FlowFixMe +import fs from 'fs/promises'; +import {readdirSync} from 'fs'; +import path from 'path'; +import {spawn as _spawn} from 'child_process'; +// $FlowFixMe +import {parseArgs, styleText} from 'util'; + +const supportsEmoji = isUnicodeSupported(); + +// Fallback symbols for Windows from https://en.wikipedia.org/wiki/Code_page_437 +const success: string = supportsEmoji ? '✨' : '√'; +const error: string = supportsEmoji ? '🚨' : '×'; + +const {positionals} = parseArgs({ + allowPositionals: true, + options: {}, +}); + +let template = positionals[0]; +if (!template) { + let packageManager = getCurrentPackageManager()?.name; + console.error( + `Usage: ${packageManager ?? 'npm'} create