|  | 
|  | 1 | +/* eslint-disable prettier/prettier */ | 
| 1 | 2 | /** | 
| 2 | 3 |  * Webpack plugin for creating a virtual module with CodeMirror language data | 
| 3 | 4 |  * | 
| 4 | 5 |  * This plugin creates a virtual module that can be imported using: | 
| 5 |  | - * import { extensionsToLanguages, sortedLangNames } from '@@codemirrorLanguageData@@'; | 
|  | 6 | + * import { extensionToLang, langNames } from '@@codemirrorLanguageData@@'; | 
| 6 | 7 |  * | 
| 7 | 8 |  * The plugin generates the data from @codemirror/language-data at build time | 
| 8 | 9 |  * and makes it available as a virtual module without writing files to disk. | 
| 9 | 10 |  */ | 
| 10 | 11 | 
 | 
|  | 12 | +const path = require( 'path' ); | 
|  | 13 | + | 
| 11 | 14 | class CodeMirrorLanguageDataPlugin { | 
|  | 15 | +	/** @readonly */ | 
| 12 | 16 | 	static virtualModuleName = '@@codemirrorLanguageData@@'; | 
| 13 | 17 | 
 | 
|  | 18 | +	/** @type {?string} */ | 
|  | 19 | +	virtualModulePath = null; | 
|  | 20 | + | 
|  | 21 | +	/** | 
|  | 22 | +	 * Plugin apply method. | 
|  | 23 | +	 * | 
|  | 24 | +	 * @param {import('webpack').Compiler} compiler -- The Webpack compiler instance. | 
|  | 25 | +	 */ | 
| 14 | 26 | 	apply( compiler ) { | 
| 15 |  | -		compiler.hooks.normalModuleFactory.tap( 'CodeMirrorLanguageDataPlugin', factory => { | 
| 16 |  | -			factory.hooks.beforeResolve.tap( 'CodeMirrorLanguageDataPlugin', resolveData => { | 
| 17 |  | -				const request = resolveData.request; | 
|  | 27 | +		// Create virtual file path | 
|  | 28 | +		this.virtualModulePath = path.resolve( | 
|  | 29 | +			compiler.context, | 
|  | 30 | +			this.constructor.virtualModuleName | 
|  | 31 | +		); | 
| 18 | 32 | 
 | 
| 19 |  | -				if ( request === CodeMirrorLanguageDataPlugin.virtualModuleName ) { | 
| 20 |  | -					// Generate the language data | 
| 21 |  | -					const moduleContent = this.generateModuleContent(); | 
|  | 33 | +		// Hook into afterEnvironment to set up the virtual file system | 
|  | 34 | +		compiler.hooks.afterEnvironment.tap( this.constructor.name, () => { | 
|  | 35 | +			const content = this.generateModuleContent(); | 
|  | 36 | +			this.writeVirtualFile( | 
|  | 37 | +				compiler.inputFileSystem, | 
|  | 38 | +				this.virtualModulePath, | 
|  | 39 | +				content | 
|  | 40 | +			); | 
|  | 41 | +		} ); | 
| 22 | 42 | 
 | 
| 23 |  | -					// Create a virtual module | 
| 24 |  | -					resolveData.request = 'data:text/javascript,' + encodeURIComponent( moduleContent ); | 
|  | 43 | +		// Hook into normalModuleFactory to intercept module resolution | 
|  | 44 | +		compiler.hooks.normalModuleFactory.tap( 'CodeMirrorLanguageDataPlugin', factory => { | 
|  | 45 | +			factory.hooks.beforeResolve.tap( | 
|  | 46 | +				'CodeMirrorLanguageDataPlugin', | 
|  | 47 | +				resolveData => { | 
|  | 48 | +					if ( | 
|  | 49 | +						resolveData.request === | 
|  | 50 | +						this.constructor.virtualModuleName | 
|  | 51 | +					) { | 
|  | 52 | +						resolveData.request = this.virtualModulePath; | 
|  | 53 | +					} | 
| 25 | 54 | 				} | 
| 26 |  | -			} ); | 
|  | 55 | +			); | 
| 27 | 56 | 		} ); | 
| 28 | 57 | 	} | 
| 29 | 58 | 
 | 
| @@ -54,6 +83,88 @@ class CodeMirrorLanguageDataPlugin { | 
| 54 | 83 | 		return `export const extensionToLang = ${ JSON.stringify( extensionsToLanguages ) }; | 
| 55 | 84 | export const langNames = ${ JSON.stringify( sortedLangNames ) };`; | 
| 56 | 85 | 	} | 
|  | 86 | + | 
|  | 87 | +	/** | 
|  | 88 | +	 * Write the file. | 
|  | 89 | +	 * | 
|  | 90 | +	 * @param {import('webpack').InputFileSystem} fs - Virtual file system. | 
|  | 91 | +	 * @param {string} filePath - Path. | 
|  | 92 | +	 * @param {string} contents - File contents. | 
|  | 93 | +	 */ | 
|  | 94 | +	writeVirtualFile( fs, filePath, contents ) { | 
|  | 95 | +		const stats = { | 
|  | 96 | +			isFile: () => true, | 
|  | 97 | +			isDirectory: () => false, | 
|  | 98 | +			isBlockDevice: () => false, | 
|  | 99 | +			isCharacterDevice: () => false, | 
|  | 100 | +			isSymbolicLink: () => false, | 
|  | 101 | +			isFIFO: () => false, | 
|  | 102 | +			isSocket: () => false, | 
|  | 103 | +			dev: 8675309, | 
|  | 104 | +			nlink: 1, | 
|  | 105 | +			uid: 501, | 
|  | 106 | +			gid: 20, | 
|  | 107 | +			rdev: 0, | 
|  | 108 | +			blksize: 4096, | 
|  | 109 | +			ino: Math.random(), | 
|  | 110 | +			mode: 33188, | 
|  | 111 | +			size: contents ? contents.length : 0, | 
|  | 112 | +			blocks: Math.floor( contents ? contents.length / 4096 : 0 ), | 
|  | 113 | +			atime: new Date(), | 
|  | 114 | +			mtime: new Date(), | 
|  | 115 | +			ctime: new Date(), | 
|  | 116 | +			birthtime: new Date(), | 
|  | 117 | +		}; | 
|  | 118 | + | 
|  | 119 | +		// Store the file content | 
|  | 120 | +		const virtualData = { | 
|  | 121 | +			contents, | 
|  | 122 | +			stats, | 
|  | 123 | +		}; | 
|  | 124 | + | 
|  | 125 | +		// Patch the filesystem methods | 
|  | 126 | +		const originalReadFileSync = fs.readFileSync; | 
|  | 127 | +		const originalStatSync = fs.statSync; | 
|  | 128 | +		const originalReadFile = fs.readFile; | 
|  | 129 | +		const originalStat = fs.stat; | 
|  | 130 | + | 
|  | 131 | +		// readFileSync | 
|  | 132 | +		fs.readFileSync = function ( filename, options ) { | 
|  | 133 | +			if ( filename === filePath ) { | 
|  | 134 | +				return virtualData.contents; | 
|  | 135 | +			} | 
|  | 136 | +			return originalReadFileSync.call( this, filename, options ); | 
|  | 137 | +		}.bind( this ); | 
|  | 138 | + | 
|  | 139 | +		// statSync | 
|  | 140 | +		fs.statSync = function ( filename, options ) { | 
|  | 141 | +			if ( filename === filePath ) { | 
|  | 142 | +				return virtualData.stats; | 
|  | 143 | +			} | 
|  | 144 | +			return originalStatSync.call( this, filename, options ); | 
|  | 145 | +		}.bind( this ); | 
|  | 146 | + | 
|  | 147 | +		fs.readFile = function ( filename, options, callback ) { | 
|  | 148 | +			if ( typeof options === 'function' ) { | 
|  | 149 | +				callback = options; | 
|  | 150 | +				options = undefined; | 
|  | 151 | +			} | 
|  | 152 | +			if ( filename === filePath ) { | 
|  | 153 | +				callback( null, virtualData.contents ); | 
|  | 154 | +				return; | 
|  | 155 | +			} | 
|  | 156 | +			return originalReadFile.call( this, filename, options, callback ); | 
|  | 157 | +		}.bind( this ); | 
|  | 158 | + | 
|  | 159 | +		// stat (async) | 
|  | 160 | +		fs.stat = function ( filename, callback ) { | 
|  | 161 | +			if ( filename === filePath ) { | 
|  | 162 | +				callback( null, virtualData.stats ); | 
|  | 163 | +				return; | 
|  | 164 | +			} | 
|  | 165 | +			return originalStat.call( this, filename, callback ); | 
|  | 166 | +		}.bind( this ); | 
|  | 167 | +	} | 
| 57 | 168 | } | 
| 58 | 169 | 
 | 
| 59 | 170 | module.exports = CodeMirrorLanguageDataPlugin; | 
0 commit comments