1 | // Esbuild-Plugin-MXN-Copy - Esbuild plugin for copying assets into the output directory of your bundle
|
2 | // Copyright (C) 2022 Ilya Zimnovich <zimnovich@gmail.com>
|
3 | //
|
4 | // On Rollup plugin documentation see:
|
5 | // - https://github.com/rollup/rollup/blob/master/docs/05-plugin-development.md
|
6 |
|
7 | ;
|
8 | const fs = require("fs");
|
9 | const path = require("path");
|
10 |
|
11 | /**
|
12 | * copy file to dir
|
13 | * @param src
|
14 | * @param dest
|
15 | * @returns {Promise<any>}
|
16 | */
|
17 | // Inspired by rollup-plugin-copy-files
|
18 | //
|
19 | const copyFile = function(src, dst) {
|
20 | const rd = createReadStream(src);
|
21 | const wr = createWriteStream(dst);
|
22 | return new Promise((resolve, reject) => {
|
23 | rd.on("error", reject);
|
24 | wr.on("error", reject);
|
25 | wr.on("finish", resolve);
|
26 | rd.pipe(wr);
|
27 | }).catch((error) => {
|
28 | rd.destroy();
|
29 | wr.end();
|
30 | throw error;
|
31 | });
|
32 | }
|
33 |
|
34 | // See:
|
35 | // - https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
|
36 | //
|
37 | // source <String> Note: if source is a directory it will copy everything inside of this directory, not the entire directory itself
|
38 | // target <String> Note: if source is a file, target cannot be a directory
|
39 |
|
40 | const copyFolderRecursive = async function(source, target)
|
41 | {
|
42 | // Check if target folder needs to be created
|
43 | // const targetFolder = path.join( target, path.basename( source ) );
|
44 | const targetExists = await fs.promises.lstat(target).then(r => true).catch(e => false);
|
45 | if ( !targetExists ) {
|
46 | await fs.promises.mkdir(target, { recursive: true });
|
47 | }
|
48 |
|
49 | // Copy files recursively
|
50 | const sourceStats = await fs.promises.lstat(source);
|
51 |
|
52 | if (sourceStats.isDirectory() ) {
|
53 | const dirEntries = await fs.promises.opendir(source);
|
54 |
|
55 | for await (const dirEntry of dirEntries) {
|
56 | const curSource = path.join(source, dirEntry.name);
|
57 | const curTarget = path.join(target, dirEntry.name);
|
58 | const curSourceStats = await fs.promises.lstat(curSource);
|
59 | if (curSourceStats.isDirectory() ) {
|
60 | await copyFolderRecursive(curSource, curTarget);
|
61 | }
|
62 | if (curSourceStats.isFile() ) {
|
63 | await fs.promises.copyFile(curSource, curTarget);
|
64 | }
|
65 | }
|
66 | }
|
67 | }
|
68 |
|
69 | // https://esbuild.github.io/plugins/#svelte-plugin
|
70 | //
|
71 | // An esbuild plugin is an object with a name and a setup function. They are passed
|
72 | // in an array to the build API call. The setup function is run once for each build
|
73 | // API call.
|
74 | //
|
75 | // A callback added using onResolve will be run on each import path in each module
|
76 | // that esbuild builds. The callback can customize how esbuild does path resolution.
|
77 | // For example, it can intercept import paths and redirect them somewhere else. It
|
78 | // can also mark paths as external. A callback added using onLoad will be run for
|
79 | // each unique path/namespace pair that has not been marked as external. Its job is
|
80 | // to return the contents of the module and to tell esbuild how to interpret it.
|
81 | //
|
82 | // Register an on-start callback to be notified when a new build starts. This triggers
|
83 | // for all builds, not just the initial build, so it's especially useful for incremental
|
84 | // builds, watch mode, and the serve API. Register an on-end callback to be notified
|
85 | // when a new build ends. This triggers for all builds, not just the initial build,
|
86 | // so it's especially useful for incremental builds, watch mode, and the serve API.
|
87 | //
|
88 | // Plugins can access the initial build options from within the setup method. This lets
|
89 | // you inspect how the build is configured as well as modify the build options before
|
90 | // the build starts.
|
91 |
|
92 | module.exports = function (options) {
|
93 | return {
|
94 | name: "mxn-copy",
|
95 | setup(build) {
|
96 | const defaults = { // Setting default options
|
97 | copy: [],
|
98 | verbose: false,
|
99 | restrictive: false
|
100 | };
|
101 |
|
102 | // Mixing mandatory and user provided arguments
|
103 | options = Object.assign(defaults, options);
|
104 | let sourceDir = "";
|
105 | // const options = build.initialOptions;
|
106 | // path.join(path.dirname(build.initialOptions.outfile), to)
|
107 | // let src = options.src || './static'
|
108 | // let dest = options.dest || '../public'
|
109 |
|
110 | // input: [ 'src/index.js' ]
|
111 | //
|
112 | /*
|
113 | if (Array.isArray(input) ) {
|
114 | sourceDir = path.dirname(input[0]);
|
115 | } else {
|
116 | sourceDir = path.dirname(input);
|
117 | }*/
|
118 |
|
119 | build.onEnd(async function(result) {
|
120 | return Promise.all(
|
121 | options.copy.map(async asset => {
|
122 | try {
|
123 | let fromPath = asset.from;
|
124 | let toPath = asset.to;
|
125 |
|
126 | // Correct toPath if it ends with slash ("/" or "\\")
|
127 | if (toPath.endsWith("/") || toPath.endsWith("\\") ) {
|
128 | toPath += path.basename(fromPath);
|
129 | }
|
130 |
|
131 | if (options.restrictive) { // Restrictive mode
|
132 | // Check input path
|
133 | const relFromPath = path.relative(sourceDir, fromPath);
|
134 | if (relFromPath.startsWith("..") ) {
|
135 | throw new Error("Assets to copy should reside within the input directory");
|
136 | }
|
137 |
|
138 | // Check output path
|
139 | const relToPath = path.relative(destDir, toPath);
|
140 | if (relToPath.startsWith("..") ) {
|
141 | throw new Error("Resulting copies should reside within the output directory");
|
142 | }
|
143 |
|
144 | fromPath = path.join(sourceDir, relFromPath);
|
145 | toPath = path.join(destDir, relToPath);
|
146 | }
|
147 |
|
148 | if (options.verbose) {
|
149 | console.log("Copying asset from %j to %j", fromPath, toPath);
|
150 | }
|
151 |
|
152 | const absFromPath = path.resolve(fromPath);
|
153 | const absToPath = path.resolve(toPath);
|
154 |
|
155 | // Check if source exists
|
156 | // Tests a user's permissions for the file or directory specified by path
|
157 | // await fs.promises.access(absAssetPath);
|
158 | try {
|
159 | const stats = await fs.promises.lstat(absFromPath);
|
160 | if (stats.isFile() ) {
|
161 | // Copying file. See:
|
162 | // - https://stackoverflow.com/questions/11293857/fastest-way-to-copy-a-file-in-node-js
|
163 | await fs.promises.copyFile(absFromPath, absToPath);
|
164 | }
|
165 | if (stats.isDirectory() ) {
|
166 | // Copying directory
|
167 | await copyFolderRecursive(absFromPath, absToPath);
|
168 | }
|
169 | }
|
170 | catch (e) {
|
171 | console.warn(`Asset ${asset.from} does not exist. ${e}`);
|
172 | }
|
173 | }
|
174 | catch (e) {
|
175 | console.warn(`Could not copy ${asset.from} because of an error: ${e}`);
|
176 | }
|
177 | })
|
178 | );
|
179 | });
|
180 | }
|
181 | }
|
182 | }
|