UNPKG

7.44 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 { pathnameIsInside, pathnameToRelativePath, hrefToScheme } from "@jsenv/href"
31import {
32 pathnameToOperatingSystemPath,
33 operatingSystemPathToPathname,
34} from "@jsenv/operating-system-path"
35import { writeOrUpdateSourceMappingURL } from "./helpers/source-mapping-url.js"
36
37export const bundleToCompilationResult = (
38 { rollupBundle, arrayOfAbstractHref },
39 { projectPathname, entryRelativePath, sourcemapAssetPath, sourcemapPath = sourcemapAssetPath },
40) => {
41 const sources = []
42 const sourcesContent = []
43 const assets = []
44 const assetsContent = []
45
46 const output = rollupBundle.output
47 const main = output[0]
48 const mainRollupSourcemap = main.map
49
50 const mainDependencyMap = rollupSourcemapToDependencyMap({
51 rollupSourcemap: mainRollupSourcemap,
52 projectPathname,
53 entryRelativePath,
54 arrayOfAbstractHref,
55 })
56
57 const addDependencyMap = (dependencyMap) => {
58 Object.keys(dependencyMap).forEach((relativePath) => {
59 if (arrayOfAbstractHref.includes(`file://${projectPathname}${relativePath}`)) return
60 if (sources.includes(relativePath)) return
61
62 sources.push(relativePath)
63 sourcesContent.push(dependencyMap[relativePath])
64 })
65 }
66
67 // main file
68 const mainSourceContent = writeOrUpdateSourceMappingURL(main.code, sourcemapPath)
69 // main sourcemap
70 assets.push(sourcemapAssetPath.slice(2))
71 const mainSourcemap = {
72 ...mainRollupSourcemap,
73 ...dependencyMapToSourcemapSubset(mainDependencyMap),
74 }
75 assetsContent.push(JSON.stringify(mainSourcemap, null, " "))
76 // main dependencies
77 addDependencyMap(mainDependencyMap)
78
79 output.slice(1).forEach((chunk) => {
80 const chunkRollupSourcemap = chunk.map
81 const chunkDependencyMap = rollupSourcemapToDependencyMap({
82 rollupSourcemap: chunkRollupSourcemap,
83 projectPathname,
84 entryRelativePath,
85 arrayOfAbstractHref,
86 })
87
88 // chunk file
89 assets.push(chunk.fileName)
90 const chunkSourceContent = writeOrUpdateSourceMappingURL(chunk.code, `./${chunk.fileName}.map`)
91 assetsContent.push(chunkSourceContent)
92 // chunk sourcemap
93 assets.push(`${chunk.fileName}.map`)
94 const chunkSourcemap = {
95 ...chunkRollupSourcemap,
96 ...dependencyMapToSourcemapSubset(chunkDependencyMap),
97 }
98 assetsContent.push(JSON.stringify(chunkSourcemap, null, " "))
99 // chunk dependencies
100 addDependencyMap(chunkDependencyMap)
101 })
102
103 return {
104 contentType: "application/javascript",
105 compiledSource: mainSourceContent,
106 sources,
107 sourcesContent,
108 assets,
109 assetsContent,
110 }
111}
112
113const dependencyMapToSourcemapSubset = (dependencyMap) => {
114 const sources = Object.keys(dependencyMap)
115 const sourcesContent = sources.map((source) => {
116 // for sourcemap the source content of a json file is the js
117 // transformation of that json
118 if (source.endsWith(".json")) return `export default ${dependencyMap[source]}`
119 return dependencyMap[source]
120 })
121 return {
122 sources,
123 sourcesContent,
124 }
125}
126
127const rollupSourcemapToDependencyMap = ({
128 rollupSourcemap,
129 projectPathname,
130 entryRelativePath,
131 arrayOfAbstractHref,
132}) => {
133 const dependencyMap = {}
134
135 rollupSourcemap.sources.forEach((source, index) => {
136 if (hrefToScheme(source)) {
137 // source is absolute
138 return
139 }
140
141 // source is relative
142 const sourcePath = resolve(
143 dirname(pathnameToOperatingSystemPath(`${projectPathname}${entryRelativePath}`)),
144 source,
145 )
146 const sourcePathname = operatingSystemPathToPathname(sourcePath)
147
148 if (!pathnameIsInside(sourcePathname, projectPathname)) {
149 throw createSourceOutsideProjectError({
150 sourcePath,
151 projectPathname,
152 source,
153 file: entryRelativePath,
154 })
155 }
156
157 const sourceRelativePath = pathnameToRelativePath(sourcePathname, projectPathname)
158 const isAbstract = arrayOfAbstractHref.includes(
159 `file://${projectPathname}${sourceRelativePath}`,
160 )
161
162 let dependencyContent
163 const sourcesContent = rollupSourcemap.sourcesContent || []
164 if (index in sourcesContent) {
165 // abstract ressource cannot be found on filesystem
166 // so trust what we found in sourcesContent
167 if (isAbstract) {
168 dependencyContent = sourcesContent[index]
169 }
170 // .json file source content is not the one inside the file
171 // because jsenv-rollup-plugin transforms it to
172 // export default, so we must set back the
173 // right file content
174 else if (sourceRelativePath.endsWith(".json")) {
175 dependencyContent = readSourceContent({
176 projectPathname,
177 sourceRelativePath,
178 entryRelativePath,
179 })
180 } else {
181 dependencyContent = sourcesContent[index]
182 }
183 } else {
184 if (isAbstract) {
185 throw createAbstractSourceMissingError({ sourceRelativePath, entryRelativePath })
186 }
187 dependencyContent = readSourceContent({
188 projectPathname,
189 sourceRelativePath,
190 entryRelativePath,
191 })
192 }
193
194 dependencyMap[sourceRelativePath] = dependencyContent
195 })
196
197 return dependencyMap
198}
199
200const readSourceContent = ({ projectPathname, sourceRelativePath, entryRelativePath }) => {
201 const sourcePath = pathnameToOperatingSystemPath(`${projectPathname}${sourceRelativePath}`)
202 // this could be async but it's ok for now
203 // making it async could be harder than it seems
204 // because sourcesContent must be in sync with sources
205 try {
206 const buffer = readFileSync(sourcePath)
207 return String(buffer)
208 } catch (e) {
209 if (e && e.code === "ENOENT") {
210 throw createSourceNotFoundError({
211 sourcePath,
212 projectPathname,
213 source: sourceRelativePath,
214 file: entryRelativePath,
215 })
216 }
217 throw e
218 }
219}
220
221const createAbstractSourceMissingError = ({ source, file }) =>
222 new Error(`an abstract file source content is missing.
223source: ${source}
224file: ${file}`)
225
226const createSourceNotFoundError = ({ sourcePath, projectPathname, source, file }) =>
227 new Error(`a file source cannot be found.
228source path: ${sourcePath}
229project path: ${pathnameToOperatingSystemPath(projectPathname)}
230source: ${source}
231file: ${file}`)
232
233const createSourceOutsideProjectError = ({ sourcePath, projectPathname, source, file }) =>
234 new Error(`a file source is not inside project.
235source path: ${sourcePath}
236project path: ${pathnameToOperatingSystemPath(projectPathname)}
237source: ${source}
238file: ${file}`)