1+ // Copyright 2024 Fastly Inc.
2+ // License: the Apache License v2.0 with LLVM Exceptions.
3+ // See https://llvm.org/LICENSE.txt for license information.
4+ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
6+ import regexpuc from 'regexpu-core' ;
7+ import { parse } from 'acorn' ;
8+ import MagicString from 'magic-string' ;
9+ import { simple as simpleWalk } from 'acorn-walk' ;
10+
11+ const PREAMBLE = `;{
12+ // Precompiled regular expressions
13+ const precompile = (r) => { r.exec('a'); r.exec('\\u1000'); };` ;
14+ const POSTAMBLE = '}' ;
15+
16+ /// Emit a block of javascript that will pre-compile the regular expressions given. As spidermonkey
17+ /// will intern regular expressions, duplicating them at the top level and testing them with both
18+ /// an ascii and utf8 string should ensure that they won't be re-compiled when run in the fetch
19+ /// handler.
20+ export function precompile ( source , filename = '<input>' , moduleMode = false ) {
21+ const magicString = new MagicString ( source , {
22+ filename,
23+ } ) ;
24+
25+ const ast = parse ( source , {
26+ ecmaVersion : 'latest' ,
27+ sourceType : moduleMode ? 'module' : 'script' ,
28+ } ) ;
29+
30+ const precompileCalls = [ ] ;
31+ simpleWalk ( ast , {
32+ Literal ( node ) {
33+ if ( ! node . regex ) return ;
34+ let transpiledPattern ;
35+ try {
36+ transpiledPattern = regexpuc ( node . regex . pattern , node . regex . flags , {
37+ unicodePropertyEscapes : 'transform' ,
38+ } ) ;
39+ } catch {
40+ // swallow regex parse errors here to instead throw them at the engine level
41+ // this then also avoids regex parser bugs being thrown unnecessarily
42+ transpiledPattern = node . regex . pattern ;
43+ }
44+ const transpiledRegex = `/${ transpiledPattern } /${ node . regex . flags } ` ;
45+ precompileCalls . push ( `precompile(${ transpiledRegex } );` ) ;
46+ magicString . overwrite ( node . start , node . end , transpiledRegex ) ;
47+ } ,
48+ } ) ;
49+
50+ if ( ! precompileCalls . length ) return source ;
51+
52+ magicString . prepend ( `${ PREAMBLE } ${ precompileCalls . join ( '\n' ) } ${ POSTAMBLE } ` ) ;
53+
54+ // When we're ready to pipe in source maps:
55+ // const map = magicString.generateMap({
56+ // source: 'source.js',
57+ // file: 'converted.js.map',
58+ // includeContent: true
59+ // });
60+
61+ return magicString . toString ( ) ;
62+ }
0 commit comments