UNPKG

4.51 kBJavaScriptView Raw
1/*
2 * Copyright 2017-present Sonatype, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17const fs = require('fs-extra');
18const path = require('path');
19
20/**
21 * A Webpack plugin that copies the raw source of all imported modules to a separate directory, enabling
22 * external analysis of _just_ the files that get included in the bundles. Within the destination folder,
23 * modules are laid out in a subdirectory structure matching the original files' relative location to the
24 * directory in which webpack runs. Note that any source files whose relative path is outside of the current directory
25 * will have the `..` parts of its path replaced with `__..__` when copied into the destination tree.
26 */
27/* global Promise */
28module.exports = class WebpackCopyModulesPlugin {
29 constructor(options) {
30 // this.destination is the absolute path to the destination folder
31 this.destination = path.resolve(process.cwd(), options.destination);
32 this.includePackageJsons = !!options.includePackageJsons;
33 }
34
35 apply(compiler) {
36 // NOTE: this is only available in webpack 5+, but that's the only version we want to check for anyway
37 const webpackVersion = compiler.webpack && compiler.webpack.version;
38
39 compiler.hooks.emit.tapPromise('WebpackCopyModulesPlugin', this.handleEmit.bind(this, webpackVersion));
40 }
41
42 async handleEmit(webpackVersion, compilation) {
43 const me = this,
44 fileDependencies = new Set(),
45 isWebpack5Plus = !!webpackVersion && webpackVersion.match(/^(\d+)\./)[1] >= 5;
46
47 // add all fileDependencies that are actual files (parent directories are included in
48 // compilation.fileDependencies)
49 for (const fileDep of compilation.fileDependencies) {
50 const exists = await fs.pathExists(fileDep),
51 isFile = exists && (await fs.lstat(fileDep)).isFile();
52
53 if (isFile) {
54 fileDependencies.add(fileDep);
55 }
56 }
57
58 // Webpack 5 already includes the package.json files, so no need for this step there
59 const packageJsons = !isWebpack5Plus && this.includePackageJsons ?
60 this.findPackageJsonPaths(fileDependencies) : [];
61
62 return Promise.all([...fileDependencies, ...packageJsons].map(function(file) {
63 const relativePath = replaceParentDirReferences(path.relative(process.cwd(), file)),
64 destPath = path.join(me.destination, relativePath),
65 destDir = path.dirname(destPath);
66
67 return fs.pathExists(file)
68 .then(exists => exists ?
69 fs.mkdirs(destDir).then(() => fs.copy(file, destPath, { overwrite: false })) :
70 Promise.resolve()
71 );
72 }));
73 }
74
75 findPackageJsonPaths(filePaths) {
76 const packageJsons = new Set(),
77
78 // dirs for which a package.json search has already been conducted.
79 // If the package.json search algo ends up in one of these dirs it knows it can stop searching
80 dirsAlreadySearchedForPackageJson = new Set();
81
82 // find associated package.json files for each fileDependency
83 filePaths.forEach(function(file) {
84 let dirPath = path.dirname(file),
85 oldDirPath;
86
87 // until we reach the root
88 while (dirPath !== oldDirPath) {
89 if (dirsAlreadySearchedForPackageJson.has(dirPath)) {
90 return;
91 }
92 else {
93 dirsAlreadySearchedForPackageJson.add(dirPath);
94
95 const packageJsonPath = path.join(dirPath, 'package.json');
96
97 if (fs.pathExistsSync(packageJsonPath)) {
98 packageJsons.add(packageJsonPath);
99 return;
100 }
101 else {
102 // loop again to check next parent dir
103 oldDirPath = dirPath;
104 dirPath = path.dirname(dirPath);
105 }
106 }
107 }
108 });
109
110 return packageJsons;
111 }
112};
113
114/**
115 * Go through the path and replace all `..` parts with `__..__`
116 */
117function replaceParentDirReferences(inputPath) {
118 const pathParts = inputPath.split(path.sep);
119
120 return pathParts.map(part => part === '..' ? '__..__' : part).join(path.sep);
121}