UNPKG

7.96 kBJavaScriptView Raw
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"use strict";
8const fs = require("fs");
9const 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//
19const 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
40const 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
92module.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}