index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. const path = require('path');
  2. const AddWorkerEntryPointPlugin = require('./plugins/AddWorkerEntryPointPlugin');
  3. const INCLUDE_LOADER_PATH = require.resolve('./loaders/include');
  4. const IGNORED_IMPORTS = {
  5. [resolveMonacoPath('vs/language/typescript/lib/typescriptServices')]: [
  6. 'fs',
  7. 'path',
  8. 'os',
  9. 'crypto',
  10. 'source-map-support',
  11. ],
  12. };
  13. const MONACO_EDITOR_API_PATHS = [
  14. resolveMonacoPath('vs/editor/editor.main'),
  15. resolveMonacoPath('vs/editor/editor.api')
  16. ];
  17. const WORKER_LOADER_PATH = resolveMonacoPath('vs/editor/common/services/editorSimpleWorker');
  18. const EDITOR_MODULE = {
  19. label: 'editorWorkerService',
  20. entry: undefined,
  21. worker: {
  22. id: 'vs/editor/editor',
  23. entry: 'vs/editor/editor.worker',
  24. output: 'editor.worker.js',
  25. fallback: undefined
  26. },
  27. alias: undefined,
  28. };
  29. const LANGUAGES = require('./languages');
  30. const FEATURES = require('./features');
  31. function resolveMonacoPath(filePath) {
  32. return require.resolve(path.join('../esm', filePath));
  33. }
  34. const languagesById = fromPairs(
  35. flatMap(toPairs(LANGUAGES), ([id, language]) =>
  36. [id, ...(language.alias || [])].map((label) => [label, { label, ...language }])
  37. )
  38. );
  39. const featuresById = mapValues(FEATURES, (feature, key) => ({ label: key, ...feature }))
  40. class MonacoWebpackPlugin {
  41. constructor(webpack, options = {}) {
  42. const languages = options.languages || Object.keys(languagesById);
  43. const features = options.features || Object.keys(featuresById);
  44. const output = options.output || '';
  45. this.webpack = webpack;
  46. this.options = {
  47. languages: languages.map((id) => languagesById[id]).filter(Boolean),
  48. features: features.map(id => featuresById[id]).filter(Boolean),
  49. output,
  50. };
  51. }
  52. apply(compiler) {
  53. const webpack = this.webpack;
  54. const { languages, features, output } = this.options;
  55. const publicPath = getPublicPath(compiler);
  56. const modules = [EDITOR_MODULE, ...languages, ...features];
  57. const workers = modules.map(
  58. ({ label, alias, worker }) => worker && ({ label, alias, ...worker })
  59. ).filter(Boolean);
  60. const rules = createLoaderRules(languages, features, workers, publicPath);
  61. const plugins = createPlugins(webpack, workers, output);
  62. addCompilerRules(compiler, rules);
  63. addCompilerPlugins(compiler, plugins);
  64. }
  65. }
  66. function addCompilerRules(compiler, rules) {
  67. const compilerOptions = compiler.options;
  68. const moduleOptions = compilerOptions.module || (compilerOptions.module = {});
  69. const existingRules = moduleOptions.rules || (moduleOptions.rules = []);
  70. existingRules.push(...rules);
  71. }
  72. function addCompilerPlugins(compiler, plugins) {
  73. plugins.forEach((plugin) => plugin.apply(compiler));
  74. }
  75. function getPublicPath(compiler) {
  76. return compiler.options.output && compiler.options.output.publicPath || '';
  77. }
  78. function stripTrailingSlash(string) {
  79. return string.replace(/\/$/, '');
  80. }
  81. function createLoaderRules(languages, features, workers, publicPath) {
  82. if (!languages.length && !features.length) { return []; }
  83. const languagePaths = languages.map(({ entry }) => entry).filter(Boolean);
  84. const featurePaths = features.map(({ entry }) => entry).filter(Boolean);
  85. const workerPaths = workers.reduce((acc, { label, output }) => Object.assign(acc, {
  86. [label]: `${publicPath ? `${stripTrailingSlash(publicPath)}/` : ''}${output}`,
  87. }), {});
  88. const globals = {
  89. 'MonacoEnvironment': `((paths) => ({ getWorkerUrl: (moduleId, label) => paths[label] }))(${
  90. JSON.stringify(workerPaths, null, 2)
  91. })`,
  92. };
  93. return [
  94. {
  95. test: MONACO_EDITOR_API_PATHS,
  96. use: [
  97. {
  98. loader: INCLUDE_LOADER_PATH,
  99. options: {
  100. globals,
  101. pre: featurePaths.map((importPath) => resolveMonacoPath(importPath)),
  102. post: languagePaths.map((importPath) => resolveMonacoPath(importPath)),
  103. },
  104. },
  105. ],
  106. },
  107. ];
  108. }
  109. function createPlugins(webpack, workers, outputPath) {
  110. const workerFallbacks = workers.reduce((acc, { id, fallback }) => (fallback ? Object.assign(acc, {
  111. [id]: resolveMonacoPath(fallback)
  112. }) : acc), {});
  113. return [
  114. ...Object.keys(IGNORED_IMPORTS).map((id) =>
  115. createIgnoreImportsPlugin(webpack, id, IGNORED_IMPORTS[id])
  116. ),
  117. ...uniqBy(workers, ({ id }) => id).map(({ id, entry, output }) =>
  118. new AddWorkerEntryPointPlugin(webpack, {
  119. id,
  120. entry: resolveMonacoPath(entry),
  121. filename: path.join(outputPath, output),
  122. plugins: [
  123. createContextPlugin(webpack, WORKER_LOADER_PATH, {}),
  124. new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
  125. ],
  126. })
  127. ),
  128. ...(workerFallbacks ? [createContextPlugin(webpack, WORKER_LOADER_PATH, workerFallbacks)] : []),
  129. ];
  130. }
  131. function createContextPlugin(webpack, filePath, contextPaths) {
  132. return new webpack.ContextReplacementPlugin(
  133. new RegExp(`^${path.dirname(filePath)}$`),
  134. '',
  135. contextPaths
  136. );
  137. }
  138. function createIgnoreImportsPlugin(webpack, targetPath, ignoredModules) {
  139. return new webpack.IgnorePlugin(
  140. new RegExp(`^(${ignoredModules.map((id) => `(${id})`).join('|')})$`),
  141. new RegExp(`^${path.dirname(targetPath)}$`)
  142. );
  143. }
  144. function flatMap(items, iteratee) {
  145. return items.map(iteratee).reduce((acc, item) => [...acc, ...item], []);
  146. }
  147. function toPairs(object) {
  148. return Object.keys(object).map((key) => [key, object[key]]);
  149. }
  150. function fromPairs(values) {
  151. return values.reduce((acc, [key, value]) => Object.assign(acc, { [key]: value }), {});
  152. }
  153. function mapValues(object, iteratee) {
  154. return Object.keys(object).reduce(
  155. (acc, key) => Object.assign(acc, { [key]: iteratee(object[key], key) }),
  156. {}
  157. );
  158. }
  159. function uniqBy(items, iteratee) {
  160. const keys = {};
  161. return items.reduce((acc, item) => {
  162. const key = iteratee(item);
  163. if (key in keys) { return acc; }
  164. keys[key] = true;
  165. acc.push(item);
  166. return acc;
  167. }, []);
  168. }
  169. module.exports = MonacoWebpackPlugin;