diff --git a/README.md b/README.md index 5032bb0..0c45894 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ monaco.editor.create(document.getElementById('container'), { Options can be passed in to `MonacoWebpackPlugin`. They can be used to generate a smaller editor bundle by selecting only certain languages or only certain editor features: -* `output` (`string`) - custom output path for worker scripts, relative to the main webpack `output.path`. - * default value: `''`. +* `filename` (`string`) - custom filename template for worker scripts, respects the same options as [loader-utils' interpolateName](https://github.com/webpack/loader-utils#interpolatename). Useful for adding content-based hashes so that files can be served with long-lived caching headers. + * default value: `'[name].worker.js'`. * `publicPath` (`string`) - custom public path for worker scripts, overrides the public path from which files generated by this plugin will be served. This wins out over Webpack's dynamic runtime path and can be useful to avoid attempting to load workers cross-origin when using a CDN for other static resources. Use e.g. '/' if you want to load your resources from the current origin.. * default value: `''`. * `languages` (`string[]`) - include only a subset of the languages supported. diff --git a/index.d.ts b/index.d.ts index 38b0702..2919c69 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,12 +1,6 @@ import { Plugin } from 'webpack' interface IMonacoEditorWebpackPluginOpts { - /** - * custom output path for worker scripts, relative to the main webpack `output.path`. - * Defaults to ''. - */ - output?: string; - /** * Include only a subset of the languages supported. */ @@ -18,6 +12,12 @@ interface IMonacoEditorWebpackPluginOpts { */ features?: string[]; + /** + * Specify a filename template to use for generated files. + * Use e.g. '[name].worker.[contenthash].js' to include content-based hashes. + */ + filename?: string; + /** * Override the public path from which files generated by this plugin will be served. * This wins out over Webpack's dynamic runtime path and can be useful to avoid attempting to load workers cross- diff --git a/index.js b/index.js index 2fe0c41..04654d1 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ const path = require('path'); const webpack = require('webpack'); +const loaderUtils = require('loader-utils'); +const fs = require('fs'); + const AddWorkerEntryPointPlugin = require('./plugins/AddWorkerEntryPointPlugin'); const INCLUDE_LOADER_PATH = require.resolve('./loaders/include'); @@ -9,17 +12,28 @@ const EDITOR_MODULE = { worker: { id: 'vs/editor/editor', entry: 'vs/editor/editor.worker', - output: 'editor.worker.js', fallback: undefined }, }; const LANGUAGES = require('./languages'); const FEATURES = require('./features'); +/** + * Return a resolved path for a given Monaco file. + */ function resolveMonacoPath(filePath) { return require.resolve(path.join('monaco-editor/esm', filePath)); } +/** + * Return the interpolated final filename for a worker, respecting the file name template. + */ +function getWorkerFilename(filename, entry) { + return loaderUtils.interpolateName({resourcePath: entry}, filename, { + content: fs.readFileSync(resolveMonacoPath(entry)) + }); +} + const languagesById = mapValues(LANGUAGES, (language, id) => mixin({ label: id }, language)); const featuresById = mapValues(FEATURES, (feature, key) => mixin({ label: key }, feature)) @@ -51,20 +65,20 @@ class MonacoWebpackPlugin { this.options = { languages: languages.map((id) => languagesById[id]).filter(Boolean), features: features.map(id => featuresById[id]).filter(Boolean), - output: options.output || '', - publicPath: options.publicPath || '' + filename: options.filename || "[name].worker.js", + publicPath: options.publicPath || '', }; } apply(compiler) { - const { languages, features, output, publicPath } = this.options; + const { languages, features, filename, publicPath } = this.options; const compilationPublicPath = getCompilationPublicPath(compiler); const modules = [EDITOR_MODULE].concat(languages).concat(features); const workers = modules.map( ({ label, worker }) => worker && (mixin({ label }, worker)) ).filter(Boolean); - const rules = createLoaderRules(languages, features, workers, output, publicPath, compilationPublicPath); - const plugins = createPlugins(workers, output); + const rules = createLoaderRules(languages, features, workers, filename, publicPath, compilationPublicPath); + const plugins = createPlugins(workers, filename); addCompilerRules(compiler, rules); addCompilerPlugins(compiler, plugins); } @@ -84,11 +98,11 @@ function getCompilationPublicPath(compiler) { return compiler.options.output && compiler.options.output.publicPath || ''; } -function createLoaderRules(languages, features, workers, outputPath, pluginPublicPath, compilationPublicPath) { +function createLoaderRules(languages, features, workers, filename, pluginPublicPath, compilationPublicPath) { if (!languages.length && !features.length) { return []; } const languagePaths = flatArr(languages.map(({ entry }) => entry).filter(Boolean)); const featurePaths = flatArr(features.map(({ entry }) => entry).filter(Boolean)); - const workerPaths = fromPairs(workers.map(({ label, output }) => [label, path.join(outputPath, output)])); + const workerPaths = fromPairs(workers.map(({ label, entry }) => [label, getWorkerFilename(filename, entry)])); if (workerPaths['typescript']) { // javascript shares the same worker workerPaths['javascript'] = workerPaths['typescript']; @@ -145,14 +159,14 @@ function createLoaderRules(languages, features, workers, outputPath, pluginPubli ]; } -function createPlugins(workers, outputPath) { +function createPlugins(workers, filename) { return ( [] - .concat(uniqBy(workers, ({ id }) => id).map(({ id, entry, output }) => + .concat(uniqBy(workers, ({ id }) => id).map(({ id, entry }) => new AddWorkerEntryPointPlugin({ id, entry: resolveMonacoPath(entry), - filename: path.join(outputPath, output), + filename: getWorkerFilename(filename, entry), plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }), ], diff --git a/languages.js b/languages.js index 7caeb79..95e618b 100644 --- a/languages.js +++ b/languages.js @@ -39,7 +39,6 @@ module.exports = { worker: { id: 'vs/language/css/cssWorker', entry: 'vs/language/css/css.worker', - output: 'css.worker.js', fallback: 'vs/language/css/cssWorker', }, }, @@ -71,7 +70,6 @@ module.exports = { worker: { id: 'vs/language/html/htmlWorker', entry: 'vs/language/html/html.worker', - output: 'html.worker.js', fallback: 'vs/language/html/htmlWorker', }, }, @@ -92,7 +90,6 @@ module.exports = { worker: { id: 'vs/language/json/jsonWorker', entry: 'vs/language/json/json.worker', - output: 'json.worker.js', fallback: 'vs/language/json/jsonWorker', }, }, @@ -216,7 +213,6 @@ module.exports = { worker: { id: 'vs/language/typescript/tsWorker', entry: 'vs/language/typescript/ts.worker', - output: 'typescript.worker.js', fallback: 'vs/language/typescript/tsWorker', }, }, diff --git a/package-lock.json b/package-lock.json index d82ae00..972b6b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1485,9 +1485,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -1498,8 +1496,6 @@ "are-we-there-yet": { "version": "1.1.5", "bundled": true, - "dev": true, - "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -1529,9 +1525,7 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", @@ -1547,9 +1541,7 @@ }, "core-util-is": { "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "debug": { "version": "4.1.1", @@ -1568,9 +1560,7 @@ }, "delegates": { "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "detect-libc": { "version": "1.0.3", @@ -1585,6 +1575,19 @@ "optional": true, "requires": { "minipass": "^2.2.1" + }, + "dependencies": { + "minipass": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + } } }, "fs.realpath": { @@ -1607,6 +1610,18 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" + }, + "dependencies": { + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + } } }, "glob": { @@ -1659,9 +1674,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -1672,17 +1685,13 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "minimatch": { "version": "3.0.4", @@ -1716,6 +1725,19 @@ "optional": true, "requires": { "minipass": "^2.2.1" + }, + "dependencies": { + "minipass": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + } } }, "mkdirp": { @@ -1774,9 +1796,7 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "npm-packlist": { "version": "1.4.1", @@ -1786,6 +1806,15 @@ "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" + }, + "dependencies": { + "npm-bundled": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "dev": true, + "optional": true + } } }, "npmlog": { @@ -1798,13 +1827,24 @@ "console-control-strings": "~1.1.0", "gauge": "~2.7.3", "set-blocking": "~2.0.0" + }, + "dependencies": { + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + } } }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -1851,9 +1891,7 @@ }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "rc": { "version": "1.2.8", @@ -1878,8 +1916,6 @@ "readable-stream": { "version": "2.3.6", "bundled": true, - "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1888,6 +1924,13 @@ "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + } } }, "rimraf": { @@ -1938,8 +1981,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1949,17 +1990,20 @@ "string_decoder": { "version": "1.1.1", "bundled": true, - "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + } } }, "strip-ansi": { "version": "3.0.1", "bundled": true, - "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1987,15 +2031,11 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true + "bundled": true }, "wide-align": { "version": "1.1.3", "bundled": true, - "dev": true, - "optional": true, "requires": { "string-width": "^1.0.2 || 2" } @@ -2713,7 +2753,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -3472,7 +3512,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } }