-
Notifications
You must be signed in to change notification settings - Fork 471
fix: stop shipping source maps in the published package #8464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+93
−0
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| /* | ||
| * This script runs at pack time (`prepack`), before the tarball is assembled. | ||
| * | ||
| * We build with `sourceMap` and `declarationMap` enabled so that local development against `dist/` | ||
| * has working maps. Those maps are useless to end users, though: they reference `../src/*.ts`, and | ||
| * `src` is not in the package's `files` list, so every map in a published install points at a file | ||
| * that isn't there. They accounted for roughly half of the published package, so we strip them | ||
| * here rather than shipping dead weight. | ||
| * | ||
| * This is idempotent — `npm publish` is invoked more than once per release (see | ||
| * `.github/workflows/release-please.yml`), and the second run should be a no-op. | ||
| */ | ||
|
|
||
| import { readdir, rm, readFile, stat, writeFile } from 'node:fs/promises' | ||
| import path from 'node:path' | ||
| import { fileURLToPath } from 'node:url' | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)) | ||
| const DIST_DIR = path.join(__dirname, '..', 'dist') | ||
| // `tsc --incremental` decides what to emit from this file alone, not from what's on disk. Stripping | ||
| // maps out from under it would otherwise leave a subsequent local build convinced it has nothing to | ||
| // do, silently yielding a `dist/` with no maps (or, after `npm run clean`, no `dist/` at all). | ||
| const BUILD_INFO_FILE = path.join(__dirname, '..', 'tsconfig.build.tsbuildinfo') | ||
|
|
||
| // Matches the trailing `//# sourceMappingURL=...` annotation left behind once the map is gone. | ||
| const SOURCE_MAPPING_URL_RE = /^\/\/# sourceMappingURL=.*$\n?/gm | ||
|
|
||
| const walk = async (dir) => { | ||
| let entries | ||
| try { | ||
| entries = await readdir(dir, { withFileTypes: true }) | ||
| } catch (error) { | ||
| if (error.code === 'ENOENT') { | ||
| return [] | ||
| } | ||
| throw error | ||
| } | ||
|
|
||
| const files = await Promise.all( | ||
| entries.map(async (entry) => { | ||
| const entryPath = path.join(dir, entry.name) | ||
| return entry.isDirectory() ? walk(entryPath) : [entryPath] | ||
| }), | ||
| ) | ||
| return files.flat() | ||
| } | ||
|
|
||
| const stripSourceMaps = async () => { | ||
| const files = await walk(DIST_DIR) | ||
|
|
||
| if (files.length === 0) { | ||
| console.error('strip-source-maps: no dist/ output found, nothing to do') | ||
| return | ||
| } | ||
|
|
||
| const maps = files.filter((file) => file.endsWith('.map')) | ||
| const annotated = files.filter((file) => file.endsWith('.js') || file.endsWith('.d.ts')) | ||
|
|
||
| // Collect sizes first and sum at the end: `total += await size(file)` would read `total` before | ||
| // awaiting and clobber every concurrent update. | ||
| const bytesRemoved = ( | ||
| await Promise.all( | ||
| maps.map(async (file) => { | ||
| const { size } = await stat(file) | ||
| await rm(file) | ||
| return size | ||
| }), | ||
| ) | ||
| ).reduce((total, size) => total + size, 0) | ||
|
|
||
| let annotationsRemoved = 0 | ||
| await Promise.all( | ||
| annotated.map(async (file) => { | ||
| const contents = await readFile(file, 'utf8') | ||
| const stripped = contents.replace(SOURCE_MAPPING_URL_RE, '') | ||
| if (stripped !== contents) { | ||
| annotationsRemoved += 1 | ||
| await writeFile(file, stripped) | ||
| } | ||
| }), | ||
| ) | ||
|
|
||
| await rm(BUILD_INFO_FILE, { force: true }) | ||
|
|
||
| console.error( | ||
| `strip-source-maps: removed ${maps.length.toString()} map file(s) (${(bytesRemoved / 1024 / 1024).toFixed( | ||
| 2, | ||
| )} MB) and ${annotationsRemoved.toString()} sourceMappingURL annotation(s)`, | ||
| ) | ||
| } | ||
|
|
||
| await stripSourceMaps() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: netlify/cli
Length of output: 6772
🏁 Script executed:
Repository: netlify/cli
Length of output: 6463
🌐 Web query:
TypeScript incremental build tsbuildinfo output file missing still emits official documentation💡 Result:
When using TypeScript, the.tsbuildinfo file is an essential component for incremental compilation, but its absence does not prevent the compiler from emitting output files. According to official TypeScript documentation and behavior: 1. Standard Incremental/Composite Builds: If your project is configured with --incremental or --composite, TypeScript uses the.tsbuildinfo file to track project state and optimize build times. If this file is missing, the compiler treats the project as "out of date," performs a full rebuild, and generates a new.tsbuildinfo file [1][2]. 2. Project References (--build Mode): As of TypeScript 5.6, the compiler now consistently emits a.tsbuildinfo file for any project involved in a --build (tsc -b) invocation, regardless of whether --incremental or --composite is explicitly enabled [3]. This is designed to track state for future builds and to support features like --noCheck [3]. 3. Emit Behavior: The TypeScript compiler is designed to always emit your JavaScript (and declaration) files if the source files are valid, even if the.tsbuildinfo file is missing or corrupted [1][2]. If the file is missing, you may experience a longer build time because the compiler cannot leverage cached information, but the output files will still be produced [2]. If you find that your build is not emitting files when a.tsbuildinfo file is missing, this is typically due to a configuration error or an underlying compilation issue (such as syntax errors in your source code) rather than the missing file itself [4]. You can force a clean, fresh rebuild by manually deleting the.tsbuildinfo file, which is a common troubleshooting step for resolving build state inconsistencies [5].
Citations:
🌐 Web query:
site:github.com/microsoft/TypeScript incremental build deleted output file tsbuildinfo emits missing output💡 Result:
When using TypeScript's incremental build mode (
"incremental": true), the compiler relies on thetsconfig.tsbuildinfofile to determine if a build is necessary [1][2]. If you manually delete output files (like.js,.d.ts, or.mapfiles) from your file system but leave thetsconfig.tsbuildinfofile intact, the TypeScript compiler assumes the previous build results are still valid and will not regenerate the missing files [1][3]. This behavior is considered "working as intended" by the TypeScript team, as checking the existence of all output files on every build would impose significant performance costs [3]. To resolve this issue, you can use one of the following approaches: 1. Recommended Workaround: Runtsc --build --clean(if using project references/build mode) or manually delete thetsconfig.tsbuildinfofile before running the build again [4][2][3]. This forces the compiler to perform a fresh build. 2. Force Rebuild: Usetsc --build --force(ortsc -b -f) to ignore existing incremental information and rebuild all files [3]. 3. Automation: If your workflow involves frequently deleting output directories (e.g., during CI/CD or switching branches), ensure that your clean script also deletes the correspondingtsconfig.tsbuildinfofile [4][2][5]. Note that TypeScript does not automatically track or clean up files in the output directory that are no longer part of your project (e.g., if you rename a source file) [6]. This is also by design to support various project structures and avoid unintended deletions [6]. If you require stricter build environments, community tools like Wireit are often used to manage build lifecycles more explicitly [6].Citations:
🏁 Script executed:
Repository: netlify/cli
Length of output: 15242
🏁 Script executed (no clone):
Length of output: 16338
🏁 Script executed (no clone):
Length of output: 2411
🏁 Script executed (no clone):
Length of output: 9873
Delete
BUILD_INFO_FILEbefore returning whendist/is missing or empty. The clean script removes onlydist/, and TypeScript incremental builds can trust staletsconfig.build.tsbuildinfoand skip regenerating deleted outputs.🤖 Prompt for AI Agents