ModuleScopePlugin.js 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. 'use strict';
  8. const chalk = require('chalk');
  9. const path = require('path');
  10. const os = require('os');
  11. class ModuleScopePlugin {
  12. constructor(appSrc, allowedFiles = []) {
  13. this.appSrcs = Array.isArray(appSrc) ? appSrc : [appSrc];
  14. this.allowedFiles = new Set(allowedFiles);
  15. }
  16. apply(resolver) {
  17. const { appSrcs } = this;
  18. resolver.hooks.file.tapAsync(
  19. 'ModuleScopePlugin',
  20. (request, contextResolver, callback) => {
  21. // Unknown issuer, probably webpack internals
  22. if (!request.context.issuer) {
  23. return callback();
  24. }
  25. if (
  26. // If this resolves to a node_module, we don't care what happens next
  27. request.descriptionFileRoot.indexOf('/node_modules/') !== -1 ||
  28. request.descriptionFileRoot.indexOf('\\node_modules\\') !== -1 ||
  29. // Make sure this request was manual
  30. !request.__innerRequest_request
  31. ) {
  32. return callback();
  33. }
  34. // Resolve the issuer from our appSrc and make sure it's one of our files
  35. // Maybe an indexOf === 0 would be better?
  36. if (
  37. appSrcs.every(appSrc => {
  38. const relative = path.relative(appSrc, request.context.issuer);
  39. // If it's not in one of our app src or a subdirectory, not our request!
  40. return relative.startsWith('../') || relative.startsWith('..\\');
  41. })
  42. ) {
  43. return callback();
  44. }
  45. const requestFullPath = path.resolve(
  46. path.dirname(request.context.issuer),
  47. request.__innerRequest_request
  48. );
  49. if (this.allowedFiles.has(requestFullPath)) {
  50. return callback();
  51. }
  52. // Find path from src to the requested file
  53. // Error if in a parent directory of all given appSrcs
  54. if (
  55. appSrcs.every(appSrc => {
  56. const requestRelative = path.relative(appSrc, requestFullPath);
  57. return (
  58. requestRelative.startsWith('../') ||
  59. requestRelative.startsWith('..\\')
  60. );
  61. })
  62. ) {
  63. const scopeError = new Error(
  64. `You attempted to import ${chalk.cyan(
  65. request.__innerRequest_request
  66. )} which falls outside of the project ${chalk.cyan(
  67. 'src/'
  68. )} directory. ` +
  69. `Relative imports outside of ${chalk.cyan(
  70. 'src/'
  71. )} are not supported.` +
  72. os.EOL +
  73. `You can either move it inside ${chalk.cyan(
  74. 'src/'
  75. )}, or add a symlink to it from project's ${chalk.cyan(
  76. 'node_modules/'
  77. )}.`
  78. );
  79. Object.defineProperty(scopeError, '__module_scope_plugin', {
  80. value: true,
  81. writable: false,
  82. enumerable: false,
  83. });
  84. callback(scopeError, request);
  85. } else {
  86. callback();
  87. }
  88. }
  89. );
  90. }
  91. }
  92. module.exports = ModuleScopePlugin;