UNPKG

7.52 kBJavaScriptView Raw
1/*
2
3bundleToCompilationResult does two things:
4
51. Change every relative path inside rollup sourcemap to an "absolute" version.
6Something like `../../importMap.json` becomes `/importMap.json`.
7In the process // sourceMappingURL comment of the file referencing the sourcemap is updated.
8
9We need this because vscode is configured with
10```json
11{
12 "sourceMapPathOverrides": {
13 "/*": "${workspaceFolder}/*"
14 },
15```
16And we need to do that because I struggled to make vscode work with relative notations.
17
182. Return { compiledSource, sources, sourcesContent, assets, assetsContent }
19It is usefull because this object can be used to create a cache for the bundle.
20This object is used by serveCompiledFile.
21
22One thing to keep in mind:
23the sourcemap.sourcesContent will contains a json file transformed to js
24while sourcesContent will contain the json file raw source because the corresponding
25json file etag is used to invalidate the cache
26*/
27
28import { readFileSync } from "fs"
29import { dirname, resolve } from "path"
30import {
31 pathnameToOperatingSystemPath,
32 pathnameIsInside,
33 pathnameToRelativePathname,
34 operatingSystemPathToPathname,
35} from "@jsenv/operating-system-path"
36import { writeOrUpdateSourceMappingURL } from "./helpers/source-mapping-url.js"
37
38export const bundleToCompilationResult = (
39 { rollupBundle, relativePathAbstractArray },
40 {
41 projectPathname,
42 compileIntoRelativePath,
43 entryRelativePath,
44 sourcemapAssetPath,
45 sourcemapPath = sourcemapAssetPath,
46 },
47) => {
48 const sources = []
49 const sourcesContent = []
50 const assets = []
51 const assetsContent = []
52
53 const output = rollupBundle.output
54 const main = output[0]
55 const mainRollupSourcemap = main.map
56
57 const mainDependencyMap = rollupSourcemapToDependencyMap({
58 rollupSourcemap: mainRollupSourcemap,
59 projectPathname,
60 compileIntoRelativePath,
61 entryRelativePath,
62 relativePathAbstractArray,
63 })
64
65 const addDependencyMap = (dependencyMap) => {
66 Object.keys(dependencyMap).forEach((relativePath) => {
67 if (relativePathAbstractArray.includes(relativePath)) return
68 if (sources.includes(relativePath)) return
69
70 sources.push(relativePath)
71 sourcesContent.push(dependencyMap[relativePath])
72 })
73 }
74
75 // main file
76 const mainSourceContent = writeOrUpdateSourceMappingURL(main.code, sourcemapPath)
77 // main sourcemap
78 assets.push(sourcemapAssetPath.slice(2))
79 const mainSourcemap = {
80 ...mainRollupSourcemap,
81 ...dependencyMapToSourcemapSubset(mainDependencyMap),
82 }
83 assetsContent.push(JSON.stringify(mainSourcemap, null, " "))
84 // main dependencies
85 addDependencyMap(mainDependencyMap)
86
87 output.slice(1).forEach((chunk) => {
88 const chunkRollupSourcemap = chunk.map
89 const chunkDependencyMap = rollupSourcemapToDependencyMap({
90 rollupSourcemap: chunkRollupSourcemap,
91 projectPathname,
92 compileIntoRelativePath,
93 entryRelativePath,
94 relativePathAbstractArray,
95 })
96
97 // chunk file
98 assets.push(chunk.fileName)
99 const chunkSourceContent = writeOrUpdateSourceMappingURL(chunk.code, `./${chunk.fileName}.map`)
100 assetsContent.push(chunkSourceContent)
101 // chunk sourcemap
102 assets.push(`${chunk.fileName}.map`)
103 const chunkSourcemap = {
104 ...chunkRollupSourcemap,
105 ...dependencyMapToSourcemapSubset(chunkDependencyMap),
106 }
107 assetsContent.push(JSON.stringify(chunkSourcemap, null, " "))
108 // chunk dependencies
109 addDependencyMap(chunkDependencyMap)
110 })
111
112 return {
113 contentType: "application/javascript",
114 compiledSource: mainSourceContent,
115 sources,
116 sourcesContent,
117 assets,
118 assetsContent,
119 }
120}
121
122const dependencyMapToSourcemapSubset = (dependencyMap) => {
123 const sources = Object.keys(dependencyMap)
124 const sourcesContent = sources.map((source) => {
125 // for sourcemap the source content of a json file is the js
126 // transformation of that json
127 if (source.endsWith(".json")) return `export default ${dependencyMap[source]}`
128 return dependencyMap[source]
129 })
130 return {
131 sources,
132 sourcesContent,
133 }
134}
135
136const rollupSourcemapToDependencyMap = ({
137 rollupSourcemap,
138 projectPathname,
139 compileIntoRelativePath,
140 entryRelativePath,
141 relativePathAbstractArray,
142}) => {
143 const dependencyMap = {}
144
145 rollupSourcemap.sources.forEach((sourceRelativeToEntry, index) => {
146 const sourcePath = resolve(
147 dirname(
148 pathnameToOperatingSystemPath(
149 `${projectPathname}${compileIntoRelativePath}${entryRelativePath}`,
150 ),
151 ),
152 sourceRelativeToEntry,
153 )
154 const sourcePathname = operatingSystemPathToPathname(sourcePath)
155
156 if (!pathnameIsInside(sourcePathname, projectPathname)) {
157 throw createSourceOutsideProjectError({
158 sourcePath,
159 projectPathname,
160 source: sourceRelativeToEntry,
161 file: entryRelativePath,
162 })
163 }
164
165 const sourceRelativePath = pathnameToRelativePathname(sourcePathname, projectPathname)
166 const isAbstract = relativePathAbstractArray.includes(sourceRelativePath)
167
168 let dependencyContent
169 const sourcesContent = rollupSourcemap.sourcesContent || []
170 if (index in sourcesContent) {
171 // abstract ressource cannot be found on filesystem
172 // so trust what we found in sourcesContent
173 if (isAbstract) {
174 dependencyContent = sourcesContent[index]
175 }
176 // .json file source content is not the one inside the file
177 // because jsenv-rollup-plugin transforms it to
178 // export default, so we must set back the
179 // right file content
180 else if (sourceRelativePath.endsWith(".json")) {
181 dependencyContent = readSourceContent({
182 projectPathname,
183 sourceRelativePath,
184 entryRelativePath,
185 })
186 } else {
187 dependencyContent = sourcesContent[index]
188 }
189 } else {
190 if (isAbstract) {
191 throw createAbstractSourceMissingError({ sourceRelativePath, entryRelativePath })
192 }
193 dependencyContent = readSourceContent({
194 projectPathname,
195 sourceRelativePath,
196 entryRelativePath,
197 })
198 }
199
200 dependencyMap[sourceRelativePath] = dependencyContent
201 })
202
203 return dependencyMap
204}
205
206const readSourceContent = ({ projectPathname, sourceRelativePath, entryRelativePath }) => {
207 const sourcePath = pathnameToOperatingSystemPath(`${projectPathname}${sourceRelativePath}`)
208 // this could be async but it's ok for now
209 // making it async could be harder than it seems
210 // because sourcesContent must be in sync with sources
211 try {
212 const buffer = readFileSync(sourcePath)
213 return String(buffer)
214 } catch (e) {
215 if (e && e.code === "ENOENT") {
216 throw createSourceNotFoundError({
217 sourcePath,
218 projectPathname,
219 source: sourceRelativePath,
220 file: entryRelativePath,
221 })
222 }
223 throw e
224 }
225}
226
227const createAbstractSourceMissingError = ({ source, file }) =>
228 new Error(`an abstract file source content is missing.
229source: ${source}
230file: ${file}`)
231
232const createSourceNotFoundError = ({ sourcePath, projectPathname, source, file }) =>
233 new Error(`a file source cannot be found.
234source path: ${sourcePath}
235project path: ${pathnameToOperatingSystemPath(projectPathname)}
236source: ${source}
237file: ${file}`)
238
239const createSourceOutsideProjectError = ({ sourcePath, projectPathname, source, file }) =>
240 new Error(`a file source is not inside project.
241source path: ${sourcePath}
242project path: ${pathnameToOperatingSystemPath(projectPathname)}
243source: ${source}
244file: ${file}`)