js-inline-loader
Advanced tools
+137
-37
@@ -0,7 +1,7 @@ | ||
| const esprima = require('esprima'); | ||
| const escodegen = require('escodegen'); | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const esprima = require('esprima'); | ||
| const escodegen = require('escodegen'); | ||
| const INLINE_MACRO = /^([\t ]*)(\S.*)?%inline\s*\(\s*['"]([^"']+)["']\s*\)\s*\.(\w+)\s*\(([\s\S]*?)\);$/gm; | ||
| const MACRO = /%inline\s*\(\s*["'](.*?)['"]\s*\)\s*\.\s*(\w+)\s*\((\s*\))?/g; | ||
@@ -163,2 +163,7 @@ /** | ||
| // Empty functions have empty contents | ||
| if (ast.body.body.length === 0) { | ||
| return ''; | ||
| } | ||
| // Functions that have only a return statement, skip the 'return' and | ||
@@ -292,49 +297,144 @@ // render only it's value | ||
| /** | ||
| * Export the webpack replace function | ||
| * Return the inline contents of the given function from the given module with | ||
| * the given argument string. | ||
| * | ||
| * @this {loaderAPI} The function should be bound to the webpack loader API | ||
| * @param {String} gModule - The module that contains the actions | ||
| * @param {String} gFunction - The module function to inline | ||
| * @param {String} gArgs - The arguments passed to the inline function | ||
| * @returns {String} Returns the generated code for this inline function | ||
| */ | ||
| module.exports = function(source) { | ||
| this.cacheable(); | ||
| function getFunctionCode(gModule, gFunction, fnArgs) { | ||
| // Resolve filename | ||
| var filePath = path.resolve(this.context, gModule); | ||
| var contents = loadModuleContents(filePath, this.options); | ||
| if (!contents) { | ||
| this.emitError('Could not find module `' + gModule + '`'); | ||
| return '/* Missing module ' + gModule + ' */'; | ||
| } | ||
| // Replace all the %inline macro encounters | ||
| return source.replace(INLINE_MACRO, (function(m, gIndent, gAssignExpr, gFile, gFunction, gArgs) { | ||
| // Load file and extract AST | ||
| var ast; | ||
| try { | ||
| ast = esprima.parse(contents, { sourceType: 'module' }) | ||
| } catch (e) { | ||
| this.emitError('%inline("' + gModule + '"): ' + e.toString()); | ||
| return '/* Parsing error in module ' + gModule + ' */'; | ||
| } | ||
| this.addDependency(filePath); | ||
| // Resolve filename | ||
| var filePath = path.resolve(this.context, gFile); | ||
| // Locate the correct function AST | ||
| var exportedFn = getExportedFunctions(ast); | ||
| var fnAst = exportedFn[gFunction]; | ||
| if (!fnAst) { | ||
| this.emitError('%inline("' + gModule + '"): Undefined function `' + gFunction); | ||
| return '/* Unknown inline ' + gFunction + ' */'; | ||
| } | ||
| // Load file and extract AST | ||
| var ast = esprima.parse(loadModuleContents(filePath, this.options), { sourceType: 'module' }) | ||
| this.addDependency(filePath); | ||
| // Replace arguments in the function ast | ||
| for (var i=0; i<fnArgs.length; ++i) { | ||
| fnAst = replaceIdentifier( fnAst.params[i].name, fnArgs[i], fnAst ); | ||
| } | ||
| // Locate the correct function AST | ||
| var exportedFn = getExportedFunctions(ast); | ||
| var fnAst = exportedFn[gFunction]; | ||
| if (!fnAst) { | ||
| this.emitError('Trying to inline unknown function ' + gFunction + ' in module ' + gFile); | ||
| return '/* Unknown inline ' + gFunction + ' */'; | ||
| } | ||
| // Render contents | ||
| return renderFunction(fnAst); | ||
| } | ||
| // Compile the arguments ast | ||
| var fnArgs = compileArgumentAst(gArgs); | ||
| if (fnArgs.length !== fnAst.params.length) { | ||
| this.emitError('Function ' + gFunction + ' is expecting exactly ' + | ||
| fnAst.params.length + ' arguments, but got ' + fnArgs.length); | ||
| return '/* Invalid syntax for ' + gFunction + ' */'; | ||
| /** | ||
| * This function walks the AST and returns the node that calls the magic | ||
| * inline function `___js_inline_loader_inline`. | ||
| * | ||
| * @param {Object} ast - The AST to walk | ||
| * @returns {Object} Returns the AST node of the magic function | ||
| */ | ||
| function findInlineToken(ast) { | ||
| if ((ast.type === 'CallExpression') && (ast.callee.name === '___js_inline_loader_inline')) { | ||
| return ast; | ||
| } | ||
| // Walk object properties | ||
| var keys = Object.keys(ast); | ||
| for (var i=0, l=keys.length; i<l; ++i) { | ||
| var key = keys[i]; | ||
| var value = ast[key]; | ||
| // Process each item of an array | ||
| if (Array.isArray(value)) { | ||
| for (var j=0, jl=value.length; j<jl; ++j) { | ||
| if (typeof value[j] === 'object') { | ||
| var ans = findInlineToken(value[j]); | ||
| if (ans) return ans; | ||
| } | ||
| } | ||
| // And forward the checks of the objects | ||
| } else if ((typeof value === 'object') && (value !== null)) { | ||
| var ans = findInlineToken(value); | ||
| if (ans) return ans; | ||
| } | ||
| } | ||
| // Replace arguments in the function ast | ||
| for (var i=0; i<fnArgs.length; ++i) { | ||
| fnAst = replaceIdentifier( fnAst.params[i].name, fnArgs[i], fnAst ); | ||
| // Nothing found | ||
| return null; | ||
| } | ||
| /** | ||
| * Correct replacement of an `%inline` macro, using AST processing | ||
| * | ||
| * @param {String} source - The source code to proess | ||
| * @param {Function} callback - The callback to use to get replacements | ||
| * @returns {String} Returns the new source | ||
| */ | ||
| function replaceInlineFunc(source, callback) { | ||
| while (true) { | ||
| var ast; | ||
| // Parse the current source into the AST | ||
| try { | ||
| ast = esprima.parse(source, {sourceType: 'module', range: true}); | ||
| } catch (e) { | ||
| this.emitError('Inline processing failed: SyntaxError: ' + e.toString()); | ||
| return source; | ||
| } | ||
| // Compile the code and properly indent it | ||
| var code = renderFunction(fnAst); | ||
| if (gAssignExpr) { | ||
| code = gIndent + gAssignExpr + code.replace(/\r?\n/g, '\n'+gIndent+' '); | ||
| } else { | ||
| code = gIndent + code.replace(/\r?\n/g, '\n'+gIndent); | ||
| // Find an inline token | ||
| var token = findInlineToken(ast); | ||
| if (!token) { | ||
| return source; | ||
| } | ||
| return code; | ||
| }).bind(this)); | ||
| // Found a token? Callback with details | ||
| var replacement = callback( | ||
| token.arguments[0].value, | ||
| token.arguments[1].name, | ||
| token.arguments.slice(2) | ||
| ); | ||
| // Replace that part of the source with the generated code | ||
| source = source.substring(0, token.range[0]) + replacement + | ||
| source.substring(token.range[1]); | ||
| } | ||
| } | ||
| /** | ||
| * Export the webpack replace function | ||
| */ | ||
| module.exports = function(source) { | ||
| this.cacheable(); | ||
| // Convert the convenient `%macro` to a proper JS expression | ||
| var hasMacros = false; | ||
| var normSource = source.replace(MACRO, function(m, gModule, gFunction, gEmpty) { | ||
| hasMacros = true; | ||
| return '___js_inline_loader_inline(\'' + gModule + '\',' + gFunction + (gEmpty ? ')' : ','); | ||
| }); | ||
| // If we don't have macros, don't bother | ||
| if (!hasMacros) { | ||
| return source; | ||
| } | ||
| // Replace all inline functions using the AST | ||
| return replaceInlineFunc.call(this, normSource, getFunctionCode.bind(this)); | ||
| }; |
+1
-1
| { | ||
| "name": "js-inline-loader", | ||
| "version": "0.1.1", | ||
| "version": "0.2.0", | ||
| "description": "A webpack loader than enables inlining functions from other modules", | ||
@@ -5,0 +5,0 @@ "main": "js-inline-loader.js", |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
28138
10.74%383
30.27%2
100%