@@ -17,6 +17,8 @@ const app = express();
1717const compress = require ( 'compression' ) ;
1818const { Readable} = require ( 'node:stream' ) ;
1919
20+ const nodeModule = require ( 'node:module' ) ;
21+
2022app . use ( compress ( ) ) ;
2123
2224// Application
@@ -116,6 +118,88 @@ app.get('/todos', function (req, res) {
116118 ] ) ;
117119} ) ;
118120
121+ if ( process . env . NODE_ENV === 'development' ) {
122+ const rootDir = path . resolve ( __dirname , '../' ) ;
123+
124+ app . get ( '/source-maps' , async function ( req , res , next ) {
125+ try {
126+ res . set ( 'Content-type' , 'application/json' ) ;
127+ let requestedFilePath = req . query . name ;
128+
129+ let isCompiledOutput = false ;
130+ if ( requestedFilePath . startsWith ( 'file://' ) ) {
131+ // We assume that if it was prefixed with file:// it's referring to the compiled output
132+ // and if it's a direct file path we assume it's source mapped back to original format.
133+ isCompiledOutput = true ;
134+ requestedFilePath = url . fileURLToPath ( requestedFilePath ) ;
135+ }
136+
137+ const relativePath = path . relative ( rootDir , requestedFilePath ) ;
138+ if ( relativePath . startsWith ( '..' ) || path . isAbsolute ( relativePath ) ) {
139+ // This is outside the root directory of the app. Forbid it to be served.
140+ res . status = 403 ;
141+ res . write ( '{}' ) ;
142+ res . end ( ) ;
143+ return ;
144+ }
145+
146+ const sourceMap = nodeModule . findSourceMap ( requestedFilePath ) ;
147+ let map ;
148+ if ( requestedFilePath . startsWith ( 'node:' ) ) {
149+ // This is a node internal. We don't include any source code for this but we still
150+ // generate a source map for it so that we can add it to an ignoreList automatically.
151+ map = {
152+ version : 3 ,
153+ // We use the node:// protocol convention to teach Chrome DevTools that this is
154+ // on a different protocol and not part of the current page.
155+ sources : [ 'node:///' + requestedFilePath . slice ( 5 ) ] ,
156+ sourcesContent : [ '// Node Internals' ] ,
157+ mappings : 'AAAA' ,
158+ ignoreList : [ 0 ] ,
159+ sourceRoot : '' ,
160+ } ;
161+ } else if ( ! sourceMap || ! isCompiledOutput ) {
162+ // If a file doesn't have a source map, such as this file, then we generate a blank
163+ // source map that just contains the original content and segments pointing to the
164+ // original lines. If a line number points to uncompiled output, like if source mapping
165+ // was already applied we also use this path.
166+ const sourceContent = await readFile ( requestedFilePath , 'utf8' ) ;
167+ const lines = sourceContent . split ( '\n' ) . length ;
168+ // We ensure to absolute
169+ const sourceURL = url . pathToFileURL ( requestedFilePath ) ;
170+ map = {
171+ version : 3 ,
172+ sources : [ sourceURL ] ,
173+ sourcesContent : [ sourceContent ] ,
174+ // Note: This approach to mapping each line only lets you jump to each line
175+ // not jump to a column within a line. To do that, you need a proper source map
176+ // generated for each parsed segment or add a segment for each column.
177+ mappings : 'AAAA' + ';AACA' . repeat ( lines - 1 ) ,
178+ sourceRoot : '' ,
179+ // Add any node_modules to the ignore list automatically.
180+ ignoreList : requestedFilePath . includes ( 'node_modules' )
181+ ? [ 0 ]
182+ : undefined ,
183+ } ;
184+ } else {
185+ // We always set prepareStackTrace before reading the stack so that we get the stack
186+ // without source maps applied. Therefore we have to use the original source map.
187+ // If something read .stack before we did, we might observe the line/column after
188+ // source mapping back to the original file. We use the isCompiledOutput check above
189+ // in that case.
190+ map = sourceMap . payload ;
191+ }
192+ res . write ( JSON . stringify ( map ) ) ;
193+ res . end ( ) ;
194+ } catch ( x ) {
195+ res . status = 500 ;
196+ res . write ( '{}' ) ;
197+ res . end ( ) ;
198+ console . error ( x ) ;
199+ }
200+ } ) ;
201+ }
202+
119203app . listen ( 3001 , ( ) => {
120204 console . log ( 'Regional Flight Server listening on port 3001...' ) ;
121205} ) ;
0 commit comments