diff --git a/tools/isobuild/builder.js b/tools/isobuild/builder.js index 707dbb3262..ea71eb8fc1 100644 --- a/tools/isobuild/builder.js +++ b/tools/isobuild/builder.js @@ -232,6 +232,39 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` return partsOut.join(files.pathSep); } + // Checks if a file with the same path and hash was written by + // the previous builder. If it was, it adds it to the cache and makes + // sure the parent directories exist and are part of the cache. + // + // Returns true if the file was already written + usePreviousWrite(relPath, hash, sanitize) { + relPath = this._normalizeFilePath(relPath, sanitize); + + if (this.previousWrittenHashes[relPath] === hash) { + this._ensureDirectory(files.pathDirname(relPath)); + this.writtenHashes[relPath] = hash; + this.usedAsFile[relPath] = true; + return true; + } + + return false; + } + + _normalizeFilePath(relPath, sanitize) { + // Ensure no trailing slash + if (relPath.slice(-1) === files.pathSep) { + relPath = relPath.slice(0, -1); + } + + // In sanitize mode, ensure path does not contain segments like + // '..', does not contain forbidden characters, and is unique. + if (sanitize) { + relPath = this._sanitize(relPath); + } + + return relPath; + } + // Write either a buffer or the contents of a file to `relPath` (a // path to a file relative to the bundle root), creating it (and any // enclosing directories) if it doesn't exist yet. Exactly one of @@ -254,16 +287,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // // If `file` is used then it will be added to the builder's WatchSet. write(relPath, {data, file, hash, sanitize, executable, symlink}) { - // Ensure no trailing slash - if (relPath.slice(-1) === files.pathSep) { - relPath = relPath.slice(0, -1); - } - - // In sanitize mode, ensure path does not contain segments like - // '..', does not contain forbidden characters, and is unique. - if (sanitize) { - relPath = this._sanitize(relPath); - } + relPath = this._normalizeFilePath(relPath, sanitize); let getData = null; if (data) { @@ -291,7 +315,7 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}` // Write is called multiple times for assets when they have multiple urls for the same file if (this.previousWrittenHashes[relPath] !== hash && this.writtenHashes[relPath] !== hash) { - + // Builder is used to create build products, which should be read-only; // users shouldn't be manually editing automatically generated files and // expecting the results to "stick". diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 3c097a5391..1adbfe88ae 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -176,6 +176,9 @@ import { PackageRegistry } from "../../packages/meteor/define-package.js"; const SOURCE_URL_PREFIX = "meteor://\u{1f4bb}app"; +const MINIFY_PLAIN_FUNCTION = Buffer.from('function(', 'utf8'); +const MINIFY_RENAMED_FUNCTION = Buffer.from('function __minifyJs(', 'utf8'); + // files to ignore when bundling. node has no globs, so use regexps exports.ignoreFiles = [ /~$/, /^\.#/, @@ -546,6 +549,9 @@ class File { // disk). this.sourcePath = options.sourcePath; + // Allows not calculating sri when it isn't needed + this._skipSri = options.skipSri + // info is just for help with debugging the tool; it isn't written to disk or // anything. this.info = options.info || '?'; @@ -619,7 +625,9 @@ class File { hashes.push(this._inputHash); } - hashes.push(this.sri()); + if (!this._skipSri) { + hashes.push(this.sri()); + } this._hash = watch.sha1(...hashes); } @@ -628,7 +636,7 @@ class File { } sri() { - if (! this._sri) { + if (! this._sri && !this._skipSri) { this._sri = watch.sha512(this.contents()); } @@ -1160,6 +1168,7 @@ class Target { data: resource.data, cacheable: false, hash: resource.hash, + skipSri: !!resource.hash }; const file = new File(fileOptions); @@ -1326,13 +1335,10 @@ class Target { // expression, which some minifiers (e.g. UglifyJS) either fail to // parse or mistakenly eliminate as dead code. To avoid these // problems, we temporarily name the function __minifyJs. - file._contents = Buffer.from( - file.contents() - .toString("utf8") - .replace(/^\s*function\s*\(/, - "function __minifyJs("), - "utf8" - ); + file._contents = Buffer.concat([ + MINIFY_RENAMED_FUNCTION, + file.contents().slice(MINIFY_PLAIN_FUNCTION.length) + ]); dynamicFiles.push(jsf); @@ -1364,15 +1370,24 @@ class Target { function handle(source, dynamic) { source._minifiedFiles.forEach(file => { // Remove the function name __minifyJs that was added above. - file.data = file.data - .toString("utf8") - .replace(/^\s*function\s+__minifyJs\s*\(/, - "function("); + if (typeof file.data === 'string') { + file.data = Buffer.from( + file.data + .replace(/^\s*function\s+__minifyJs\s*\(/, + "function("), + "utf8" + ); + } else if (dynamic) { + file.data = Buffer.concat([ + MINIFY_PLAIN_FUNCTION, + file.data.slice(MINIFY_RENAMED_FUNCTION.length) + ]); + } const newFile = new File({ info: 'minified js', arch, - data: Buffer.from(file.data, 'utf8'), + data: file.data, hash: inputHashesByJsFile.get(source), }); @@ -2744,6 +2759,10 @@ var writeFile = Profile("bundler writeFile", function (file, builder, options) { let data = file.contents(); const hash = file.hash(); + if (builder.usePreviousWrite(file.targetPath, hash)) { + return; + } + if (options && options.sourceMapUrl) { data = addSourceMappingURL(data, options.sourceMapUrl); } else {