├── LICENSE ├── README.md ├── caching-minifier ├── .npm │ └── package │ │ ├── .gitignore │ │ ├── README │ │ └── npm-shrinkwrap.json ├── .versions ├── caching-compiler.js ├── caching-minifier.js ├── package.js └── readme.md ├── hide-production-sourcemaps ├── .versions ├── README.md ├── hide-production-sourcemaps-tests.js ├── hide-production-sourcemaps.js └── package.js ├── minifier-js-source-maps ├── .npm │ └── package │ │ ├── .gitignore │ │ ├── README │ │ └── npm-shrinkwrap.json ├── .versions ├── minifier-js-source-maps-tests.js ├── minifier-js-source-maps.js ├── package.js └── plugin │ └── minify-js.js ├── publish-standard-minifier.sh └── standard-minifier-js-sourcemaps ├── .npm └── plugin │ └── fastMinifier │ ├── .gitignore │ ├── README │ └── npm-shrinkwrap.json ├── .versions ├── package.js └── plugin ├── generate-package-map.js ├── minify-js.js └── stats.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011 - 2017 Meteor Development Group, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | ==================================================================== 25 | This license applies to all code in Meteor that is not an externally 26 | maintained library. Externally maintained libraries have their own 27 | licenses, included in the LICENSES directory. 28 | ==================================================================== -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zodern:standard-minifier-js 2 | 3 | Fast javascript minifier for Meteor apps that creates source maps 4 | 5 | Features: 6 | 7 | - Creates production source maps 8 | - Creates source maps for Meteor packages that don't use a compiler 9 | - Very fast by using the swc minifier, a faster js parser, and disk and memory caches 10 | - Compatible with Meteor 1.9 and newer. For Meteor 1.4 - 1.5, use `zodern:standard-minifier-js@3.0.0` for production source maps, and for Meteor 1.6 - 1.8 use `zodern:standard-minifier-js@4.1.1`. 11 | - Generates bundle stats for [bundle-visualizer](https://atmospherejs.com/meteor/bundle-visualizer) 12 | 13 | First, you need to remove `standard-minifier-js` from your app 14 | 15 | ```shell 16 | meteor remove standard-minifier-js 17 | ``` 18 | 19 | Then add this package with: 20 | 21 | ```shell 22 | meteor add zodern:standard-minifier-js 23 | ``` 24 | 25 | If you want to prevent access to the source maps, you can add the `zodern:hide-production-sourcemaps` package. Source maps include the original content from all of your client files, so you probably want to do this step. 26 | 27 | ```shell 28 | meteor add zodern:hide-production-sourcemaps 29 | ``` 30 | 31 | ## Error tracking 32 | 33 | Source maps allow error tracking services to show you better stack traces. I run [Monti APM](https://montiapm.com) which provides an error tracking service and can use your app's source maps with no additional config. 34 | 35 | To use with other error tracking services, you will need to upload the source maps when deploying. The source map is saved in the bundle from `meteor build` at `programs//.js.map`. You will want to upload the source maps for each web arch, and for the dynamic imports for each arch. 36 | 37 | ## Caches 38 | 39 | When deploying from CI, you will need to configure the CI to cache at least parts of the `.meteor/local` folder for the minify cache to work. Learn more at [this blog post](https://zodern.me/posts/meteor-local-folder/#caching-in-ci). 40 | 41 | ## Environment Variables 42 | 43 | `DISABLE_CLIENT_STATS` Set to `true` to disable creating the `stats.json` file used by the bundle-visualizer. This can save a few seconds during production builds for large apps. 44 | 45 | `METEOR_FASTMINIFIER_CACHE_DEBUG` Set to `true` to view the cache logs 46 | -------------------------------------------------------------------------------- /caching-minifier/.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /caching-minifier/.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /caching-minifier/.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "dependencies": { 4 | "lru-cache": { 5 | "version": "5.1.1", 6 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 7 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==" 8 | }, 9 | "yallist": { 10 | "version": "3.1.1", 11 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 12 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /caching-minifier/.versions: -------------------------------------------------------------------------------- 1 | meteor@1.11.2 2 | modules@0.19.0 3 | modules-runtime@0.13.1 4 | zodern:caching-minifier@0.5.0 5 | -------------------------------------------------------------------------------- /caching-minifier/caching-compiler.js: -------------------------------------------------------------------------------- 1 | // Copied from https://raw.githubusercontent.com/meteor/meteor/devel/packages/caching-compiler/caching-compiler.js 2 | // Using a copy allows us to remove the `ecmascript` dependency 3 | const fs = Plugin.fs; 4 | const path = Plugin.path; 5 | const createHash = Npm.require('crypto').createHash; 6 | const assert = Npm.require('assert'); 7 | const LRU = Npm.require('lru-cache'); 8 | 9 | // Base class for CachingCompiler and MultiFileCachingCompiler. 10 | module.exports = class CachingCompilerBase { 11 | constructor({ 12 | compilerName, 13 | defaultCacheSize, 14 | maxParallelism = 20, 15 | }) { 16 | this._compilerName = compilerName; 17 | this._maxParallelism = maxParallelism; 18 | const compilerNameForEnvar = compilerName.toUpperCase() 19 | .replace('/-/g', '_').replace(/[^A-Z0-9_]/g, ''); 20 | const envVarPrefix = 'METEOR_' + compilerNameForEnvar + '_CACHE_'; 21 | 22 | const debugEnvVar = envVarPrefix + 'DEBUG'; 23 | this._cacheDebugEnabled = !!process.env[debugEnvVar]; 24 | 25 | const cacheSizeEnvVar = envVarPrefix + 'SIZE'; 26 | this._cacheSize = +process.env[cacheSizeEnvVar] || defaultCacheSize; 27 | 28 | this._diskCache = null; 29 | 30 | // For testing. 31 | this._callCount = 0; 32 | 33 | // Callbacks that will be called after the linker is done processing 34 | // files, after all lazy compilation has finished. 35 | this._afterLinkCallbacks = []; 36 | } 37 | 38 | // Your subclass must override this method to define the key used to identify 39 | // a particular version of an InputFile. 40 | // 41 | // Given an InputFile (the data type passed to processFilesForTarget as part 42 | // of the Plugin.registerCompiler API), returns a cache key that represents 43 | // it. This cache key can be any JSON value (it will be converted internally 44 | // into a hash). This should reflect any aspect of the InputFile that affects 45 | // the output of `compileOneFile`. Typically you'll want to include 46 | // `inputFile.getDeclaredExports()`, and perhaps 47 | // `inputFile.getPathInPackage()` or `inputFile.getDeclaredExports` if 48 | // `compileOneFile` pays attention to them. 49 | // 50 | // Note that for MultiFileCachingCompiler, your cache key doesn't need to 51 | // include the file's path, because that is automatically taken into account 52 | // by the implementation. CachingCompiler subclasses can choose whether or not 53 | // to include the file's path in the cache key. 54 | getCacheKey(inputFile) { 55 | throw Error('CachingCompiler subclass should implement getCacheKey!'); 56 | } 57 | 58 | // Your subclass must override this method to define how a CompileResult 59 | // translates into adding assets to the bundle. 60 | // 61 | // This method is given an InputFile (the data type passed to 62 | // processFilesForTarget as part of the Plugin.registerCompiler API) and a 63 | // CompileResult (either returned directly from compileOneFile or read from 64 | // the cache). It should call methods like `inputFile.addJavaScript` 65 | // and `inputFile.error`. 66 | addCompileResult(inputFile, compileResult) { 67 | throw Error('CachingCompiler subclass should implement addCompileResult!'); 68 | } 69 | 70 | // Your subclass must override this method to define the size of a 71 | // CompilerResult (used by the in-memory cache to limit the total amount of 72 | // data cached). 73 | compileResultSize(compileResult) { 74 | throw Error('CachingCompiler subclass should implement compileResultSize!'); 75 | } 76 | 77 | // Your subclass may override this method to define an alternate way of 78 | // stringifying CompilerResults. Takes a CompileResult and returns a string. 79 | stringifyCompileResult(compileResult) { 80 | return JSON.stringify(compileResult); 81 | } 82 | // Your subclass may override this method to define an alternate way of 83 | // parsing CompilerResults from string. Takes a string and returns a 84 | // CompileResult. If the string doesn't represent a valid CompileResult, you 85 | // may want to return null instead of throwing, which will make 86 | // CachingCompiler ignore the cache. 87 | parseCompileResult(stringifiedCompileResult) { 88 | return this._parseJSONOrNull(stringifiedCompileResult); 89 | } 90 | _parseJSONOrNull(json) { 91 | try { 92 | return JSON.parse(json); 93 | } catch (e) { 94 | if (e instanceof SyntaxError) 95 | return null; 96 | throw e; 97 | } 98 | } 99 | 100 | _cacheDebug(message) { 101 | if (!this._cacheDebugEnabled) 102 | return; 103 | console.log(`CACHE(${this._compilerName}): ${message}`); 104 | } 105 | 106 | setDiskCacheDirectory(diskCache) { 107 | if (this._diskCache) 108 | throw Error('setDiskCacheDirectory called twice?'); 109 | this._diskCache = diskCache; 110 | } 111 | 112 | // Since so many compilers will need to calculate the size of a SourceMap in 113 | // their compileResultSize, this method is provided. 114 | sourceMapSize(sm) { 115 | if (!sm) return 0; 116 | // sum the length of sources and the mappings, the size of 117 | // metadata is ignored, but it is not a big deal 118 | return sm.mappings.length 119 | + (sm.sourcesContent || []).reduce(function (soFar, current) { 120 | return soFar + (current ? current.length : 0); 121 | }, 0); 122 | } 123 | 124 | // Called by the compiler plugins system after all linking and lazy 125 | // compilation has finished. 126 | afterLink() { 127 | this._afterLinkCallbacks.splice(0).forEach(callback => { 128 | callback(); 129 | }); 130 | } 131 | 132 | // Borrowed from another MIT-licensed project that benjamn wrote: 133 | // https://github.com/reactjs/commoner/blob/235d54a12c/lib/util.js#L136-L168 134 | _deepHash(val) { 135 | const hash = createHash('sha1'); 136 | let type = typeof val; 137 | 138 | if (val === null) { 139 | type = 'null'; 140 | } 141 | hash.update(type + '\0'); 142 | 143 | switch (type) { 144 | case 'object': 145 | const keys = Object.keys(val); 146 | 147 | // Array keys will already be sorted. 148 | if (!Array.isArray(val)) { 149 | keys.sort(); 150 | } 151 | 152 | keys.forEach((key) => { 153 | if (typeof val[key] === 'function') { 154 | // Silently ignore nested methods, but nevertheless complain below 155 | // if the root value is a function. 156 | return; 157 | } 158 | 159 | hash.update(key + '\0').update(this._deepHash(val[key])); 160 | }); 161 | 162 | break; 163 | 164 | case 'function': 165 | assert.ok(false, 'cannot hash function objects'); 166 | break; 167 | 168 | default: 169 | hash.update('' + val); 170 | break; 171 | } 172 | 173 | return hash.digest('hex'); 174 | } 175 | 176 | // Write the file atomically. 177 | _writeFile(filename, contents) { 178 | const tempFilename = filename + '.tmp.' + Random.id(); 179 | 180 | try { 181 | fs.writeFileSync(tempFilename, contents); 182 | fs.renameSync(tempFilename, filename); 183 | } catch (e) { 184 | // ignore errors, it's just a cache 185 | this._cacheDebug(e); 186 | } 187 | } 188 | 189 | // Helper function. Returns the body of the file as a string, or null if it 190 | // doesn't exist. 191 | _readFileOrNull(filename) { 192 | try { 193 | return fs.readFileSync(filename, 'utf8'); 194 | } catch (e) { 195 | if (e && e.code === 'ENOENT') 196 | return null; 197 | throw e; 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /caching-minifier/caching-minifier.js: -------------------------------------------------------------------------------- 1 | const LRU = Npm.require('lru-cache'); 2 | const CachingCompilerBase = require('./caching-compiler'); 3 | const crypto = require("crypto"); 4 | 5 | const fs = Plugin.fs; 6 | const path = Plugin.path; 7 | 8 | if (typeof Profile === 'undefined') { 9 | var Profile = function (label, func) { 10 | return function () { 11 | return func.apply(this, arguments); 12 | } 13 | } 14 | Profile.time = function (label, func) { 15 | func(); 16 | } 17 | } 18 | 19 | module.exports.CachingMinifier = class CachingMinifier extends CachingCompilerBase { 20 | constructor({ 21 | minifierName, 22 | defaultCacheSize, 23 | }) { 24 | super({ compilerName: minifierName, defaultCacheSize }); 25 | 26 | // Maps from a hashed cache key to the minify result. 27 | this._cache = new LRU({ 28 | max: this._cacheSize, 29 | length: (value) => this.compileResultSize(value), 30 | }); 31 | } 32 | 33 | // Your subclass should call minifyFile for each file that 34 | // is in the bundle. It takes care of caching and will 35 | // call minifyOneFile as needed. 36 | minifyFile(file) { 37 | // The hash meteor provides seems to change more than 38 | // necessary, so we create our own here based on only what 39 | // affects the minified output 40 | let key 41 | Profile.time('hash', () => { 42 | key = this._deepHash({ 43 | content: file.getContentsAsString(), 44 | sourcemap: JSON.stringify(file.getSourceMap()), 45 | }); 46 | }) 47 | let result = this._cache.get(key); 48 | let source = 'memory'; 49 | 50 | if (!result) { 51 | Profile.time('readCache', () => { 52 | result = this._readCache(key); 53 | source = 'file'; 54 | }) 55 | } 56 | if (!result) { 57 | Profile.time('minifyOneFile', () => { 58 | result = this.minifyOneFile(file); 59 | }); 60 | this._cache.set(key, result); 61 | this._writeCacheAsync(key, result); 62 | source = null; 63 | } 64 | 65 | if (source) { 66 | this._cacheDebug(`Loaded minified ${file.getPathInBundle()} from ${source} cache`) 67 | } else { 68 | this._cacheDebug(`Cache miss for ${file.getPathInBundle()} with the key "${key}"`) 69 | } 70 | 71 | return result; 72 | } 73 | 74 | // Your subclass must override this method to handle minifing a single file. 75 | // Your minifier should not call minifyOneFile directly 76 | // Instead, it should call minifyFile. 77 | // 78 | // Given an InputFile (the data type passed to processFilesForBundle as part 79 | // of the Plugin.registerMinifier API), compiles the file and returns the 80 | // result. 81 | // 82 | // This method is not called on files when a valid cache entry exists in 83 | // memory or on disk. 84 | // 85 | // This method should not call `inputFile.addJavaScript`! 86 | // That should be handled in processFilesForBundle 87 | minifyOneFile () { 88 | throw new Error('CachingMinifier subclass should implement minifyOneFile!'); 89 | } 90 | 91 | compileResultSize(result) { 92 | let sourceMapSize = 0; 93 | 94 | if (typeof result.sourcemap === 'string') { 95 | sourceMapSize = result.sourcemap.length; 96 | } else { 97 | sourceMapSize = this.sourceMapSize(result.sourcemap); 98 | } 99 | 100 | return result.code ? result.code.length + sourceMapSize : 0; 101 | } 102 | 103 | // The following methods were copied from CachingMinifier 104 | // and modified to be compatible with a wider range 105 | // of Meteor versions 106 | 107 | _cacheFilename(cacheKey) { 108 | // We want cacheKeys to be hex so that they work on any FS and never end in 109 | // .cache. 110 | if (!/^[a-f0-9]+$/.test(cacheKey)) { 111 | throw Error('bad cacheKey: ' + cacheKey); 112 | } 113 | return path.join(this._diskCache, cacheKey + '.cache'); 114 | } 115 | 116 | // Returns null if the file does not exist or can't be parsed; otherwise 117 | // returns the parsed compileResult in the file. 118 | _readAndParseCompileResultOrNull(filename) { 119 | const raw = this._readFileOrNull(filename); 120 | return this.parseCompileResult(raw); 121 | } 122 | 123 | // Load a cache entry from disk. Returns the compileResult object 124 | // and loads it into the in-memory cache too. 125 | _readCache(cacheKey) { 126 | if (!this._diskCache) { 127 | return null; 128 | } 129 | const cacheFilename = this._cacheFilename(cacheKey); 130 | const compileResult = this._readAndParseCompileResultOrNull(cacheFilename); 131 | if (!compileResult) { 132 | return null; 133 | } 134 | this._cache.set(cacheKey, compileResult); 135 | return compileResult; 136 | } 137 | 138 | _writeCacheAsync(cacheKey, compileResult) { 139 | if (!this._diskCache) 140 | return; 141 | const cacheFilename = this._cacheFilename(cacheKey); 142 | const cacheContents = this.stringifyCompileResult(compileResult); 143 | this._writeFile(cacheFilename, cacheContents); 144 | } 145 | 146 | // Write the file atomically. 147 | _writeFile(filename, contents) { 148 | const randomId = crypto.randomBytes(16).toString("hex"); 149 | const tempFilename = filename + '.tmp.' + randomId; 150 | try { 151 | fs.writeFileSync(tempFilename, contents); 152 | fs.renameSync(tempFilename, filename); 153 | } catch (e) { 154 | this._cacheDebug(e) 155 | // ignore errors, it's just a cache 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /caching-minifier/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'zodern:caching-minifier', 3 | version: '0.5.0', 4 | summary: 'An easy way to make minifier plugins cache', 5 | documentation: './readme.md', 6 | git: 'https://github.com/zodern/minify-js-sourcemaps.git' 7 | }); 8 | 9 | Npm.depends({ 10 | 'lru-cache': '5.1.1' 11 | }); 12 | 13 | Package.onUse(function(api) { 14 | api.use('isobuild:minifier-plugin@1.0.0'); 15 | api.use('modules@0.7.5'); 16 | api.mainModule('caching-minifier.js', 'server'); 17 | api.export('CachingMinifier', 'server'); 18 | }); 19 | -------------------------------------------------------------------------------- /caching-minifier/readme.md: -------------------------------------------------------------------------------- 1 | # zodern:caching-minifier 2 | 3 | An easy way to make minifier plugins cache. Extends [caching-compiler](https://atmospherejs.com/meteor/caching-compiler) to add support for minifiers. 4 | 5 | The cache key is a hash of the file's content and source map. Uses both an in-memory and on disk cache. 6 | 7 | ### Example: 8 | 9 | ```js 10 | class AwesomeMinifier extends CachingMinifier { 11 | constructor() { 12 | super({ 13 | minifierName: 'awesome', 14 | defaultCacheSize: 1024*1024*10, 15 | }); 16 | } 17 | minifyOneFile(inputFile) { 18 | // Should return a { code, sourcemap } object. 19 | return Awesomifier.minify(inputFile.getContentsAsString()); 20 | } 21 | processFilesForBundle(files, options) { 22 | // normal code for processFilesForBundle 23 | // except to minify a file call this.minifyFile(file); 24 | // this.minifyFile handles caching, and will call 25 | // this.minifyOneFile as needed 26 | } 27 | } 28 | 29 | Plugin.registerMinifier({ 30 | extensions: ['js'], 31 | archMatching: 'web', 32 | }, () => new AwesomeCompiler()); 33 | ``` 34 | -------------------------------------------------------------------------------- /hide-production-sourcemaps/.versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.0.5 2 | babel-compiler@6.18.2 3 | babel-runtime@1.0.1 4 | base64@1.0.10 5 | binary-heap@1.0.10 6 | blaze@2.1.9 7 | blaze-tools@1.0.10 8 | boilerplate-generator@1.0.11 9 | callback-hook@1.0.10 10 | check@1.2.5 11 | ddp@1.2.5 12 | ddp-client@1.3.4 13 | ddp-common@1.2.8 14 | ddp-server@1.3.14 15 | deps@1.0.12 16 | diff-sequence@1.0.7 17 | ecmascript@0.7.3 18 | ecmascript-runtime@0.3.15 19 | ejson@1.0.13 20 | geojson-utils@1.0.10 21 | html-tools@1.0.11 22 | htmljs@1.0.11 23 | id-map@1.0.9 24 | jquery@1.11.10 25 | local-test:zodern:hide-production-sourcemaps@1.0.0 26 | logging@1.1.17 27 | meteor@1.6.1 28 | minimongo@1.0.21 29 | modules@0.8.2 30 | modules-runtime@0.7.10 31 | mongo@1.1.16 32 | mongo-id@1.0.6 33 | npm-mongo@2.2.24 34 | observe-sequence@1.0.16 35 | ordered-dict@1.0.9 36 | promise@0.8.8 37 | random@1.0.10 38 | reactive-var@1.0.11 39 | retry@1.0.9 40 | routepolicy@1.0.12 41 | spacebars@1.0.13 42 | spacebars-compiler@1.0.13 43 | tinytest@1.0.12 44 | tracker@1.1.2 45 | ui@1.0.12 46 | underscore@1.0.10 47 | webapp@1.3.15 48 | webapp-hashing@1.0.9 49 | zodern:hide-production-sourcemaps@1.0.0 50 | -------------------------------------------------------------------------------- /hide-production-sourcemaps/README.md: -------------------------------------------------------------------------------- 1 | # zodern:hide-production-sourcemaps 2 | 3 | Prevents access to source maps in production and any files in your `public` folder with the `.map` file extension. 4 | 5 | Install with 6 | ``` 7 | meteor add zodern:hide-production-sourcemaps 8 | ``` 9 | 10 | `zodern:hide-production-sourcemaps` only runs in production. 11 | 12 | To temporarily disable, set the `EXPOSE_SOURCE_MAPS` environment variable to `true`. 13 | 14 | It works by preventing the Webapp package from adding the `x-sourcemap` header to javascript files, and removing the source maps from Webapp's list of static files. 15 | -------------------------------------------------------------------------------- /hide-production-sourcemaps/hide-production-sourcemaps-tests.js: -------------------------------------------------------------------------------- 1 | // Import Tinytest from the tinytest Meteor package. 2 | import { Tinytest } from "meteor/tinytest"; 3 | 4 | // Import and rename a variable exported by hide-production-sourcemaps.js. 5 | import { name as packageName } from "meteor/zodern:hide-production-sourcemaps"; 6 | 7 | // Write your tests here! 8 | // Here is an example. 9 | Tinytest.add('hide-production-sourcemaps - example', function (test) { 10 | test.equal(packageName, "hide-production-sourcemaps"); 11 | }); 12 | -------------------------------------------------------------------------------- /hide-production-sourcemaps/hide-production-sourcemaps.js: -------------------------------------------------------------------------------- 1 | const hideSourceMaps = (staticFiles) => { 2 | Object.keys(staticFiles).forEach((key) => { 3 | if (key.endsWith(".map")) { 4 | delete staticFiles[key]; 5 | return; 6 | } 7 | staticFiles[key].sourceMapUrl = false; 8 | }); 9 | } 10 | 11 | if (process.env.EXPOSE_SOURCE_MAPS !== 'true') { 12 | if (WebAppInternals.staticFilesByArch) { 13 | Object 14 | .keys(WebAppInternals.staticFilesByArch) 15 | .forEach((arch) => hideSourceMaps(WebAppInternals.staticFilesByArch[arch])); 16 | } 17 | if (WebAppInternals.staticFiles) { 18 | hideSourceMaps(WebAppInternals.staticFiles); 19 | } 20 | } else { 21 | console.warn('Source maps are not hidden since the env var EXPOSE_SOURCE_MAPS is set to "true"'); 22 | } 23 | -------------------------------------------------------------------------------- /hide-production-sourcemaps/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'zodern:hide-production-sourcemaps', 3 | version: '1.2.1', 4 | // Brief, one-line summary of the package. 5 | summary: 'Hide sourcemaps in production', 6 | // URL to the Git repository containing the source code for this package. 7 | git: 'https://github.com/zodern/minify-js-sourcemaps.git', 8 | // By default, Meteor will default to using README.md for documentation. 9 | // To avoid submitting documentation, set this field to null. 10 | documentation: 'README.md', 11 | prodOnly: true 12 | }); 13 | 14 | Package.onUse(function(api) { 15 | api.use('webapp@1.3.14||2.0.0'); 16 | api.addFiles('hide-production-sourcemaps.js', 'server'); 17 | }); 18 | 19 | Package.onTest(function(api) { 20 | api.use('tinytest'); 21 | api.use('zodern:hide-production-sourcemaps'); 22 | }); 23 | -------------------------------------------------------------------------------- /minifier-js-source-maps/.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /minifier-js-source-maps/.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /minifier-js-source-maps/.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "dependencies": { 4 | "buffer-from": { 5 | "version": "1.1.2", 6 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 7 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 8 | }, 9 | "commander": { 10 | "version": "2.20.3", 11 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 12 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 13 | }, 14 | "source-map": { 15 | "version": "0.6.1", 16 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 17 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 18 | }, 19 | "source-map-support": { 20 | "version": "0.5.20", 21 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", 22 | "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==" 23 | }, 24 | "terser": { 25 | "version": "4.8.0", 26 | "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", 27 | "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /minifier-js-source-maps/.versions: -------------------------------------------------------------------------------- 1 | babel-compiler@7.1.1 2 | ecmascript-runtime@0.7.0 3 | ecmascript-runtime-client@0.7.1 4 | ecmascript-runtime-server@0.7.0 5 | meteor@1.9.0 6 | modern-browsers@0.1.1 7 | modules@0.12.2 8 | modules-runtime@0.10.0 9 | promise@0.11.1 10 | zodern:minifier-js@3.0.0 11 | -------------------------------------------------------------------------------- /minifier-js-source-maps/minifier-js-source-maps-tests.js: -------------------------------------------------------------------------------- 1 | // Import Tinytest from the tinytest Meteor package. 2 | import { Tinytest } from "meteor/tinytest"; 3 | 4 | // Import and rename a variable exported by minifier-js-source-maps.js. 5 | import { name as packageName } from "meteor/minifier-js-source-maps"; 6 | 7 | // Write your tests here! 8 | // Here is an example. 9 | Tinytest.add('minifier-js-source-maps - example', function (test) { 10 | test.equal(packageName, "minifier-js-source-maps"); 11 | }); 12 | -------------------------------------------------------------------------------- /minifier-js-source-maps/minifier-js-source-maps.js: -------------------------------------------------------------------------------- 1 | // Write your package code here! 2 | 3 | // Variables exported by this module can be imported by other packages and 4 | // applications. See minifier-js-source-maps-tests.js for an example of importing. 5 | module.exports.name = 'minifier-js-source-maps'; 6 | -------------------------------------------------------------------------------- /minifier-js-source-maps/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'zodern:minifier-js', 3 | summary: "JavaScript minifier", 4 | version: "4.1.0", 5 | documentation: null, 6 | git: "https://github.com/zodern/minify-js-sourcemaps.git" 7 | }); 8 | 9 | Npm.depends({ 10 | "terser": "4.8.0" 11 | }); 12 | 13 | Package.onUse(function (api) { 14 | api.use('babel-compiler@6.18.2 || 7.0.0'); 15 | api.export(['meteorJsMinify']); 16 | api.addFiles(['plugin/minify-js.js'], 'server'); 17 | }); 18 | -------------------------------------------------------------------------------- /minifier-js-source-maps/plugin/minify-js.js: -------------------------------------------------------------------------------- 1 | var terser; 2 | 3 | meteorJsMinify = function (source, sourcemap, path) { 4 | var result = {}; 5 | var NODE_ENV = process.env.NODE_ENV || "development"; 6 | var sourcemap = sourcemap || undefined; 7 | 8 | terser = terser || Npm.require('terser'); 9 | 10 | try { 11 | var terserResult = terser.minify({ 12 | [path]: source 13 | }, { 14 | compress: { 15 | drop_debugger: false, 16 | unused: false, 17 | dead_code: true, 18 | global_defs: { 19 | "process.env.NODE_ENV": NODE_ENV 20 | } 21 | }, 22 | // Fix issue meteor/meteor#9866, as explained in this comment: 23 | // https://github.com/mishoo/UglifyJS2/issues/1753#issuecomment-324814782 24 | // And fix terser issue #117: https://github.com/terser-js/terser/issues/117 25 | safari10: true, 26 | sourceMap: { 27 | content: sourcemap 28 | } 29 | }); 30 | 31 | if (typeof terserResult.code === "string") { 32 | result.code = terserResult.code; 33 | result.sourcemap = terserResult.map; 34 | result.minifier = 'terser'; 35 | } else { 36 | throw terserResult.error || 37 | new Error("unknown terser.minify failure"); 38 | } 39 | } catch (e) { 40 | // Although Babel.minify can handle a wider variety of ECMAScript 41 | // 2015+ syntax, it is substantially slower than UglifyJS/terser, so 42 | // we use it only as a fallback. 43 | var options = Babel.getMinifierOptions({ 44 | inlineNodeEnv: NODE_ENV 45 | }); 46 | options.sourceMaps = true; 47 | options.inputSourceMap = sourcemap; 48 | options.sourceFileName = path; 49 | 50 | var babelResult = Babel.minify(source, options); 51 | 52 | result.code = babelResult.code; 53 | result.sourcemap = babelResult.map; 54 | result.minifier = 'babel-minify'; 55 | } 56 | 57 | return result; 58 | }; 59 | -------------------------------------------------------------------------------- /publish-standard-minifier.sh: -------------------------------------------------------------------------------- 1 | rm -r ./standard-minifier-js-sourcemaps/.npm/plugin/fastMinifier/node_modules/meteor-package-install-swc/.swc 2 | rm ./standard-minifier-js-sourcemaps/.npm/plugin/fastMinifier/node_modules/meteor-package-install-swc/.meteor-last-rebuild-version.json 3 | 4 | cd standard-minifier-js-sourcemaps 5 | meteor publish 6 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/.npm/plugin/fastMinifier/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/.npm/plugin/fastMinifier/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/.npm/plugin/fastMinifier/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "dependencies": { 4 | "@babel/parser": { 5 | "version": "7.22.7", 6 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", 7 | "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==" 8 | }, 9 | "@jridgewell/gen-mapping": { 10 | "version": "0.3.3", 11 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 12 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==" 13 | }, 14 | "@jridgewell/resolve-uri": { 15 | "version": "3.1.1", 16 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 17 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" 18 | }, 19 | "@jridgewell/set-array": { 20 | "version": "1.1.2", 21 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 22 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" 23 | }, 24 | "@jridgewell/source-map": { 25 | "version": "0.3.5", 26 | "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", 27 | "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==" 28 | }, 29 | "@jridgewell/sourcemap-codec": { 30 | "version": "1.4.15", 31 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 32 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 33 | }, 34 | "@jridgewell/trace-mapping": { 35 | "version": "0.3.21", 36 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", 37 | "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==" 38 | }, 39 | "@zodern/source-maps": { 40 | "version": "1.1.1", 41 | "resolved": "https://registry.npmjs.org/@zodern/source-maps/-/source-maps-1.1.1.tgz", 42 | "integrity": "sha512-LLdfrsMyiszk5f0jqZPWuZHAZhhIed66aYVGBI1Y8XuZ+5DnNV0AmTh+/Ib7xEbfWJuyhz69LKmcxdE/hQXsLA==" 43 | }, 44 | "acorn": { 45 | "version": "8.10.0", 46 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", 47 | "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" 48 | }, 49 | "buffer-from": { 50 | "version": "1.1.2", 51 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 52 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 53 | }, 54 | "commander": { 55 | "version": "2.20.3", 56 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 57 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 58 | }, 59 | "meteor-package-install-swc": { 60 | "version": "1.1.2", 61 | "resolved": "https://registry.npmjs.org/meteor-package-install-swc/-/meteor-package-install-swc-1.1.2.tgz", 62 | "integrity": "sha512-BZKmI4B76koc5qNibrrSanTndQTvrBr01l1AVI4qfrBRm7CCYV+2jO3QtWBk6+jdj80Mtp7NE1vWDVIJ9bg7lw==" 63 | }, 64 | "source-map": { 65 | "version": "0.6.1", 66 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 67 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 68 | }, 69 | "source-map-support": { 70 | "version": "0.5.21", 71 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 72 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==" 73 | }, 74 | "terser": { 75 | "version": "5.19.2", 76 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", 77 | "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==" 78 | }, 79 | "vlq": { 80 | "version": "2.0.4", 81 | "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", 82 | "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/.versions: -------------------------------------------------------------------------------- 1 | meteor@1.11.2 2 | modules@0.19.0 3 | modules-runtime@0.13.1 4 | zodern:caching-minifier@0.5.0 5 | zodern:standard-minifier-js@5.1.2 6 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'zodern:standard-minifier-js', 3 | version: '5.3.1', 4 | summary: 'Fast javascript minifier that creates production sourcemap', 5 | documentation: '../readme.md', 6 | git: 'https://github.com/zodern/minify-js-sourcemaps.git' 7 | }); 8 | 9 | Package.registerBuildPlugin({ 10 | name: 'fastMinifier', 11 | use: [ 12 | 'modules@0.7.5', 13 | 'zodern:caching-minifier@0.5.0' 14 | ], 15 | sources: [ 16 | 'plugin/minify-js.js', 17 | 'plugin/stats.js' 18 | ], 19 | npmDependencies: { 20 | 'meteor-package-install-swc': '1.1.2', 21 | 'acorn': '8.10.0', 22 | '@babel/parser': '7.22.7', 23 | 'terser': '5.19.2', 24 | '@zodern/source-maps': '1.1.1' 25 | } 26 | }); 27 | 28 | Package.onUse(function(api) { 29 | api.use('isobuild:minifier-plugin@1.0.0'); 30 | }); 31 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/plugin/generate-package-map.js: -------------------------------------------------------------------------------- 1 | // Some packages don't have source maps in Meteor 1 and 2. 2 | // Here we try to generate an empty source map for the file 3 | 4 | const SourceMap = require('@zodern/source-maps'); 5 | 6 | const headerRegex = /\(function\(\){\n\n\/*$\s\/\/\s*\/\/\s\/\/\s(packages\/.*)$\s\/\/\s*\/\/\s\/\/*\s\s*\/\/\n/gm; 7 | const pathRegex = /\/\/\s(packages\/.*)\/\//g; 8 | const footerRegex = /^\/{14,}\s\s}\).call\(this\);\s\s/gm; 9 | 10 | // TODO: use start index 11 | function countLinesTo(content, index) { 12 | let count = 0; 13 | let lastIndex = content.indexOf('\n'); 14 | while (lastIndex > -1 && lastIndex < index) { 15 | count += 1; 16 | lastIndex = content.indexOf('\n', lastIndex + 1); 17 | } 18 | 19 | return count; 20 | } 21 | 22 | function generateMap (content) { 23 | let map = new SourceMap(); 24 | 25 | let header; 26 | while ((header = headerRegex.exec(content)) !== null) { 27 | let start = header.index; 28 | let bannerEnd = headerRegex.lastIndex; 29 | 30 | pathRegex.lastIndex = start - 1; 31 | let filePath = pathRegex.exec(content)[1].trim(); 32 | 33 | footerRegex.lastIndex = bannerEnd; 34 | let end = footerRegex.exec(content).index; 35 | 36 | map.addEmptyMap(filePath, content.substring(bannerEnd, end), countLinesTo(content, bannerEnd)); 37 | 38 | headerRegex.lastIndex = footerRegex.lastIndex; 39 | } 40 | 41 | return map.build(); 42 | } 43 | 44 | module.exports = function (content, path) { 45 | try { 46 | return generateMap(content); 47 | } catch (error) { 48 | console.log(''); 49 | console.error(`Unable to generate source map for ${path}:`); 50 | console.error(error); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/plugin/minify-js.js: -------------------------------------------------------------------------------- 1 | const { extractModuleSizesTree } = require("./stats.js"); 2 | const { CachingMinifier } = require("meteor/zodern:caching-minifier"); 3 | const generatePackageMap = require('./generate-package-map.js'); 4 | const { CombinedFile } = require('@zodern/source-maps'); 5 | 6 | const statsEnabled = process.env.DISABLE_CLIENT_STATS !== 'true' 7 | 8 | if (typeof Profile === 'undefined') { 9 | if (Plugin.Profile) { 10 | Profile = Plugin.Profile; 11 | } else { 12 | Profile = function (label, func) { 13 | return function () { 14 | return func.apply(this, arguments); 15 | } 16 | } 17 | Profile.time = function (label, func) { 18 | func(); 19 | } 20 | } 21 | } 22 | 23 | let swc; 24 | 25 | Plugin.registerMinifier({ 26 | extensions: ['js'], 27 | archMatching: 'web' 28 | }, function () { 29 | var minifier = new MeteorBabelMinifier(); 30 | return minifier; 31 | }); 32 | 33 | class MeteorBabelMinifier extends CachingMinifier { 34 | constructor() { 35 | super({ 36 | minifierName: 'fast-minifier' 37 | }) 38 | } 39 | 40 | _minifyWithSwc(file) { 41 | swc = swc || require('meteor-package-install-swc'); 42 | const NODE_ENV = process.env.NODE_ENV || 'development'; 43 | 44 | let map = file.getSourceMap(); 45 | let content = file.getContentsAsString(); 46 | 47 | if (!map) { 48 | map = generatePackageMap(content, file.getPathInBundle()); 49 | } 50 | 51 | if (map) { 52 | map = JSON.stringify(map); 53 | } 54 | 55 | return swc.minifySync( 56 | content, 57 | { 58 | ecma: 5, 59 | compress: { 60 | drop_debugger: false, 61 | 62 | unused: true, 63 | dead_code: true, 64 | typeofs: false, 65 | 66 | global_defs: { 67 | 'process.env.NODE_ENV': NODE_ENV, 68 | }, 69 | }, 70 | sourceMap: map ? { 71 | content: map, 72 | } : undefined, 73 | safari10: true, 74 | inlineSourcesContent: true 75 | } 76 | ); 77 | } 78 | 79 | _minifyWithTerser(file) { 80 | let terser = require('terser'); 81 | const NODE_ENV = process.env.NODE_ENV || 'development'; 82 | 83 | return terser.minify(file.getContentsAsString(), { 84 | compress: { 85 | drop_debugger: false, 86 | unused: false, 87 | dead_code: true, 88 | global_defs: { 89 | "process.env.NODE_ENV": NODE_ENV 90 | } 91 | }, 92 | // Fix issue meteor/meteor#9866, as explained in this comment: 93 | // https://github.com/mishoo/UglifyJS2/issues/1753#issuecomment-324814782 94 | // And fix terser issue #117: https://github.com/terser-js/terser/issues/117 95 | safari10: true, 96 | sourceMap: { 97 | content: file.getSourceMap() 98 | } 99 | }); 100 | } 101 | 102 | minifyOneFile(file) { 103 | try { 104 | return this._minifyWithSwc(file); 105 | } catch (swcError) { 106 | try { 107 | // swc always parses as if the file is a module, which is 108 | // too strict for some Meteor packages. Try again with terser 109 | return this._minifyWithTerser(file).await(); 110 | } catch (_) { 111 | // swc has a much better error message, so we use it 112 | throw swcError; 113 | } 114 | } 115 | } 116 | } 117 | 118 | MeteorBabelMinifier.prototype.processFilesForBundle = Profile('processFilesForBundle', function (files, options) { 119 | var mode = options.minifyMode; 120 | 121 | // don't minify anything for development 122 | if (mode === 'development') { 123 | files.forEach(function (file) { 124 | let map = file.getSourceMap(); 125 | if (!map) { 126 | map = generatePackageMap(file.getContentsAsString(), file.getPathInBundle()); 127 | } 128 | 129 | file.addJavaScript({ 130 | data: file.getContentsAsBuffer(), 131 | sourceMap: map, 132 | path: file.getPathInBundle(), 133 | }); 134 | }); 135 | return; 136 | } 137 | 138 | const minifiedResults = []; 139 | const toBeAdded = { 140 | data: "", 141 | stats: Object.create(null) 142 | }; 143 | 144 | var combinedFile = new CombinedFile(); 145 | 146 | files.forEach(file => { 147 | // Don't reminify *.min.js. 148 | // FIXME: this still minifies .min.js app files since they were all combined into app.js 149 | if (/\.min\.js$/.test(file.getPathInBundle())) { 150 | minifiedResults.push({ 151 | code: file.getContentsAsString(), 152 | map: file.getSourceMap() 153 | }); 154 | } else { 155 | var minified; 156 | let label = 'minify file' 157 | if (file.getPathInBundle() === 'app/app.js') { 158 | label = 'minify app/app.js' 159 | } 160 | if (file.getPathInBundle() === 'packages/modules.js') { 161 | label = 'minify packages/modules.js' 162 | } 163 | 164 | try { 165 | Profile.time(label, () => { 166 | minified = this.minifyFile(file); 167 | }); 168 | 169 | if (!(minified && typeof minified.code === "string")) { 170 | throw new Error(); 171 | } 172 | 173 | } catch (err) { 174 | var filePath = file.getPathInBundle(); 175 | 176 | err.message += " while minifying " + filePath; 177 | throw err; 178 | } 179 | 180 | if (statsEnabled) { 181 | let tree; 182 | Profile.time('extractModuleSizesTree', () => { 183 | tree = extractModuleSizesTree(minified.code); 184 | }); 185 | 186 | if (tree) { 187 | toBeAdded.stats[file.getPathInBundle()] = 188 | [Buffer.byteLength(minified.code), tree]; 189 | } else { 190 | toBeAdded.stats[file.getPathInBundle()] = 191 | Buffer.byteLength(minified.code); 192 | } 193 | } 194 | 195 | minifiedResults.push({ 196 | file: file.getPathInBundle(), 197 | code: minified.code, 198 | map: minified.map 199 | }); 200 | } 201 | 202 | Plugin.nudge(); 203 | }); 204 | 205 | let output; 206 | Profile.time('concat', () => { 207 | minifiedResults.forEach(function (result, index) { 208 | if (index > 0) { 209 | combinedFile.addGeneratedCode('\n\n'); 210 | } 211 | 212 | let map = result.map; 213 | 214 | if (typeof map === 'string') { 215 | map = JSON.parse(result.map); 216 | } 217 | 218 | combinedFile.addCodeWithMap(result.file, { code: result.code, map }); 219 | 220 | Plugin.nudge(); 221 | }); 222 | 223 | output = combinedFile.build(); 224 | }); 225 | 226 | if (files.length) { 227 | Profile.time('addJavaScript', () => { 228 | toBeAdded.data = output.code; 229 | toBeAdded.sourceMap = output.map; 230 | files[0].addJavaScript(toBeAdded); 231 | }); 232 | } 233 | }); 234 | -------------------------------------------------------------------------------- /standard-minifier-js-sourcemaps/plugin/stats.js: -------------------------------------------------------------------------------- 1 | let Visitor; 2 | let findPossibleIndexes; 3 | let acorn = require('acorn'); 4 | 5 | try { 6 | const _Visitor = require("reify/lib/visitor.js"); 7 | Visitor = _Visitor; 8 | 9 | ({ findPossibleIndexes } = require("reify/lib/utils.js")); 10 | } catch (e) { 11 | // Meteor 2.5.2 switched from reify to @meteorjs/reify 12 | const _Visitor = require("@meteorjs/reify/lib/visitor.js"); 13 | Visitor = _Visitor; 14 | 15 | ({ findPossibleIndexes } = require("@meteorjs/reify/lib/utils.js")); 16 | } 17 | 18 | // This RegExp will be used to scan the source for calls to meteorInstall, 19 | // taking into consideration that the function name may have been mangled 20 | // to something other than "meteorInstall" by the minifier. 21 | const meteorInstallRegExp = new RegExp([ 22 | // If meteorInstall is called by its unminified name, then that's what 23 | // we should be looking for in the AST. 24 | /\b(meteorInstall)\(\{/, 25 | // If the meteorInstall function name has been minified, we can figure 26 | // out its mangled name by examining the import assignment. 27 | /\b(\w+)=Package\.modules\.meteorInstall\b/, 28 | /\b(\w+)=Package\["modules-runtime"\].meteorInstall\b/, 29 | // Sometimes uglify-es will inline (0,Package.modules.meteorInstall) as 30 | // a call expression. 31 | /\(0,Package\.modules\.(meteorInstall)\)\(/, 32 | /\(0,Package\["modules-runtime"\]\.(meteorInstall)\)\(/, 33 | ].map(exp => exp.source).join("|")); 34 | 35 | module.exports.extractModuleSizesTree = function extractModuleSizesTree(source) { 36 | const match = meteorInstallRegExp.exec(source); 37 | if (match) { 38 | let ast; 39 | try { 40 | ast = acorn.parse(source, { 41 | ecmaVersion: 'latest', 42 | sourceType: 'script', 43 | allowAwaitOutsideFunction: true, 44 | allowImportExportEverywhere: true, 45 | allowReturnOutsideFunction: true, 46 | allowHashBang: true, 47 | checkPrivateFields: false 48 | }); 49 | } catch (error) { 50 | console.log(`Error while parsing with acorn. Falling back to babel minifier. ${error}`); 51 | ast = require('@babel/parser').parse(source, { 52 | strictMode: false, 53 | sourceType: 'script', 54 | allowImportExportEverywhere: true, 55 | allowReturnOutsideFunction: true, 56 | allowUndeclaredExports: true, 57 | plugins: [ 58 | // Only plugins for stage 3 features are enabled 59 | 'importAttributes', 60 | 'explicitResourceManagement', 61 | 'decorators' 62 | ] 63 | }); 64 | } 65 | let meteorInstallName = "meteorInstall"; 66 | // The minifier may have renamed meteorInstall to something shorter. 67 | match.some((name, i) => (i > 0 && (meteorInstallName = name))); 68 | meteorInstallVisitor.visit(ast, meteorInstallName, source); 69 | return meteorInstallVisitor.tree; 70 | } 71 | } 72 | 73 | const meteorInstallVisitor = new (class extends Visitor { 74 | reset(root, meteorInstallName, source) { 75 | this.name = meteorInstallName; 76 | this.source = source; 77 | this.tree = Object.create(null); 78 | // Optimization to abandon entire subtrees of the AST that contain 79 | // nothing like the meteorInstall identifier we're looking for. 80 | this.possibleIndexes = findPossibleIndexes(source, [ 81 | meteorInstallName, 82 | ]); 83 | } 84 | 85 | visitCallExpression(path) { 86 | const node = path.getValue(); 87 | 88 | if (hasIdWithName(node.callee, this.name)) { 89 | const source = this.source; 90 | 91 | function walk(tree, expr) { 92 | if (expr.type !== "ObjectExpression") { 93 | return Buffer.byteLength(source.slice(expr.start, expr.end)); 94 | } 95 | 96 | tree = tree || Object.create(null); 97 | 98 | expr.properties.forEach(prop => { 99 | const keyName = getKeyName(prop.key); 100 | if (typeof keyName === "string") { 101 | tree[keyName] = walk(tree[keyName], prop.value); 102 | } 103 | }); 104 | 105 | return tree; 106 | } 107 | 108 | walk(this.tree, node.arguments[0]); 109 | 110 | } else { 111 | this.visitChildren(path); 112 | } 113 | } 114 | }); 115 | 116 | function hasIdWithName(node, name) { 117 | switch (node && node.type) { 118 | case "SequenceExpression": 119 | const last = node.expressions[node.expressions.length - 1]; 120 | return hasIdWithName(last, name); 121 | case "MemberExpression": 122 | return hasIdWithName(node.property, name); 123 | case "Identifier": 124 | return node.name === name; 125 | default: 126 | return false; 127 | } 128 | } 129 | 130 | function getKeyName(key) { 131 | if (key.type === "Identifier") { 132 | return key.name; 133 | } 134 | 135 | if (key.type === "StringLiteral" || 136 | key.type === "Literal") { 137 | return key.value; 138 | } 139 | 140 | return null; 141 | } 142 | --------------------------------------------------------------------------------