extension_helpers.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. var path = require('path');
  4. var findImports = require('find-imports');
  5. // Get the list of vendor files, with CodeMirror files being separate.
  6. var VENDOR_FILES = findImports('../lib/**/*.js', { flatten: true });
  7. var CODEMIRROR_FILES = VENDOR_FILES.filter(function(importPath) {
  8. return importPath.indexOf('codemirror') !== -1;
  9. });
  10. VENDOR_FILES = VENDOR_FILES.filter(function (importPath) {
  11. return (importPath.indexOf('codemirror') !== 0 &&
  12. importPath.indexOf('phosphor') !== 0 &&
  13. importPath.indexOf('jupyter-js-services') !== 0);
  14. });
  15. // Get the list of codemirror imports.
  16. var codemirrorPaths = CODEMIRROR_FILES.map(function(importPath) {
  17. return importPath.replace('.js', '');
  18. });
  19. codeMirrorPaths = codemirrorPaths.concat(['codemirror', '../lib/codemirror', '../../lib/codemirror']);
  20. /*
  21. Helper scripts to be used by extension authors (and extension extenders) in a
  22. webpack.config.json to create builds that do not include upstream extensions.
  23. Inspects the package.json of the user's package and those of its dependencies
  24. to find extensions that should be excluded.
  25. Slightly more than minimal valid setup in package.json:
  26. {
  27. "name": "foo-widget",
  28. "jupyter": {
  29. "lab": {
  30. "main": "lab-extension.js"
  31. }
  32. },
  33. "dependencies": {
  34. "jupyterlab": "*",
  35. "jupyter-js-widgets": "*"
  36. }
  37. }
  38. Example usage in webpack.config.js:
  39. var jlab_helpers = require('jupyterlab/scripts/extension_helpers');
  40. module.exports = [{
  41. entry: './src/lab/extension.js',
  42. output: {
  43. filename: 'lab-extension.js',
  44. path: '../pythonpkg/static',
  45. libraryTarget: 'this'
  46. },
  47. externals: jlab_helpers.upstream_externals(require)
  48. }];
  49. */
  50. /**
  51. * A function that mangles phosphor imports for a Webpack `externals` config.
  52. */
  53. function phosphorExternals(context, request, callback) {
  54. // All phosphor imports get mangled to use the external bundle.
  55. var regex = /^phosphor\/lib\/[\w\/]+$/;
  56. if(regex.test(request)) {
  57. var path = require.resolve(request).replace('.js', '');
  58. var index = path.indexOf('phosphor/lib');
  59. path = path.slice(index + 'phosphor/lib/'.length);
  60. var lib = 'var jupyter.phosphor.' + path.split('/').join('.');
  61. return callback(null, lib);
  62. }
  63. callback();
  64. }
  65. /**
  66. * The base Webpack `externals` config for JupyterLab itself.
  67. */
  68. var BASE_EXTERNALS = [
  69. phosphorExternals,
  70. function(context, request, callback) {
  71. // CodeMirror imports just use the external bundle.
  72. if (codemirrorPaths.indexOf(request) !== -1) {
  73. return callback(null, 'var jupyter.CodeMirror');
  74. }
  75. callback();
  76. },
  77. {
  78. 'jupyter-js-services': 'jupyter.services',
  79. 'jquery': '$',
  80. 'jquery-ui': '$'
  81. }
  82. ];
  83. /**
  84. * The default Webpack `externals` config that should be applied to
  85. * extensions of JupyterLab.
  86. */
  87. var DEFAULT_EXTERNALS = BASE_EXTERNALS.concat([
  88. function(context, request, callback) {
  89. // JupyterLab imports get mangled to use the external bundle.
  90. var regex = /^jupyterlab\/lib\/([\w\.\/]+)$/;
  91. if(regex.test(request)) {
  92. try {
  93. var path = require.resolve(request).replace('.js', '');
  94. } catch(err) {
  95. return callback();
  96. }
  97. var index = path.indexOf('jupyterlab/lib');
  98. path = path.slice(index + 'jupyterlab/lib/'.length);
  99. var lib = 'var jupyter.lab.' + path.split('/').join('.');
  100. return callback(null, lib);
  101. }
  102. callback();
  103. }
  104. ]);
  105. var DEFAULT_EXTERNALS = DEFAULT_EXTERNALS.concat(VENDOR_FILES);
  106. // determine whether the package JSON contains a JupyterLab extension
  107. function validate_extension(pkg){
  108. try {
  109. // for now, just try to load the key... could check whether file exists?
  110. pkg['jupyter']['lab']['main']
  111. return true;
  112. } catch(err) {
  113. return false;
  114. }
  115. }
  116. // the publicly exposed function
  117. function upstream_externals(_require) {
  118. // remember which packages we have seen
  119. var _seen = {},
  120. // load the user's package.json
  121. _user_pkg = _require('./package.json');
  122. // check for whether this is the root package
  123. function _is_user_pkg(pkg) {
  124. return _user_pkg['name'] === pkg['name'];
  125. }
  126. // use the provided scoped _require and the current nested location
  127. // in the `node_modules` hierarchy to resolve down to the list of externals
  128. function _load_externals(pkg_path, pkg) {
  129. var pkg_externals = [pkg['name']];
  130. try {
  131. pkg_externals = pkg_externals.concat(_require(
  132. pkg_path + '/' + pkg['jupyter']['lab']['externals']));
  133. } catch (err) {
  134. // not really worth adding any output here... usually, just the name will
  135. // suffice
  136. }
  137. return pkg_externals || [];
  138. }
  139. // return an array of strings, functions or regexen that can be deferenced by
  140. // webpack `externals` config directive
  141. // https://webpack.github.io/docs/configuration.html#externals
  142. function _find_externals(pkg_path) {
  143. var pkg = _require(pkg_path + '/package.json'),
  144. lab_config;
  145. // only visit each named package once
  146. _seen[pkg['name']] = true;
  147. if (!validate_extension(pkg)) {
  148. if (!_is_user_pkg(pkg)) {
  149. return [];
  150. } else {
  151. throw Error(
  152. pkg['name'] + ' does not contain a jupyter configuration. ' +
  153. ' Please see TODO: where?'
  154. );
  155. }
  156. }
  157. console.info("Inspecting", pkg['name'],
  158. "for upstream JupyterLab extensions...");
  159. // ok, actually start building the externals. If it is the user package,
  160. // it SHOULDN'T be an external, as this is what the user will use for their
  161. // build... otherwise, load the externals, which is probably
  162. var externals = _is_user_pkg(pkg) ?
  163. DEFAULT_EXTERNALS :
  164. _load_externals(pkg_path, pkg, _require);
  165. // Recurse through the dependencies, and collect anything that has
  166. // a JupyterLab config
  167. return Object.keys(pkg['dependencies'])
  168. .filter(function(key){ return !_seen[key]; })
  169. .reduce(function(externals, dep_name){
  170. return externals.concat(
  171. _find_externals(pkg_path + '/node_modules/' + dep_name));
  172. }, externals);
  173. }
  174. return _find_externals(".");
  175. }
  176. module.exports = {
  177. upstream_externals: upstream_externals,
  178. validate_extension: validate_extension,
  179. phosphorExternals: phosphorExternals,
  180. BASE_EXTERNALS: BASE_EXTERNALS,
  181. CODEMIRROR_FILES: CODEMIRROR_FILES,
  182. VENDOR_FILES: VENDOR_FILES,
  183. DEFAULT_EXTERNALS: DEFAULT_EXTERNALS
  184. };