UNPKG

webpack-dependency-suite

Version:

A set of Webpack plugins, loaders and utilities designed for advanced dependency resolution

125 lines (107 loc) 4.14 kB
import { CommentLoaderOptions } from '../typings/definitions' import * as path from 'path' import * as loaderUtils from 'loader-utils' import * as SourceMap from 'source-map' import * as acorn from 'acorn' import * as walk from 'acorn/dist/walk' import * as ESTree from 'estree' import * as debug from 'debug' import {appendCodeAndCallback, getRequireStrings, wrapInRequireInclude, resolveLiteral, addBundleLoader, SimpleDependency, expandAllRequiresForGlob} from '../utils/inject' const log = debug('comment-loader') export function findLiteralNodesAfterBlockComment(ast: ESTree.Program, comments: Array<acorn.Comment>, commentRegex: RegExp) { return comments .filter(comment => comment.type === 'Block') .map(commentAst => { let value = commentAst.value.trim() let match = value.match(commentRegex) return { ast: commentAst, match } }) .filter(commentMatch => !!commentMatch.match) .map(comment => { const result = walk.findNodeAfter(ast, comment.ast.end) return { commentMatch: comment.match, literal: result.node && result.node.type === 'Literal' && typeof result.node.value === 'string' ? result.node.value : '' } }) .filter(comment => !!comment.literal) } export default async function CommentLoader (this: Webpack.Core.LoaderContext, source: string, sourceMap?: SourceMap.RawSourceMap) { const query = Object.assign({}, loaderUtils.parseQuery(this.query)) as CommentLoaderOptions if (this.cacheable) { this.cacheable() } this.async() // log(`Parsing ${path.basename(this.resourcePath)}`) const comments: Array<acorn.Comment> = [] let ast: ESTree.Program | undefined = undefined const POSSIBLE_AST_OPTIONS = [{ ranges: true, locations: true, ecmaVersion: 2017, sourceType: 'module', onComment: comments }, { ranges: true, locations: true, ecmaVersion: 2017, sourceType: 'script', onComment: comments }] as Array<acorn.Options> let i = POSSIBLE_AST_OPTIONS.length while (!ast && i--) { try { comments.length = 0 ast = acorn.parse(source, POSSIBLE_AST_OPTIONS[i]); } catch(e) { // ignore the error if (!i) { this.emitError(`Error while parsing ${this.resourcePath}: ${e.message}`) return this.callback(undefined, source, sourceMap) } } } /** * @import @lazy @chunk('module') 'something' */ const commentsAndLiterals = findLiteralNodesAfterBlockComment(ast as ESTree.Program, comments, /^@import *(@lazy)? *(?:@chunk\(['"`]*([\w-]+)['"`]*\))? *(@lazy)?/) .map((cal: { commentMatch: RegExpMatchArray, literal: string }) => ({ literal: cal.literal, lazy: !!(cal.commentMatch[1] || cal.commentMatch[3]), chunk: cal.commentMatch[2] })) /** * @import('module') @lazy @chunk('module') */ const commentOnlyImports = comments .filter(c => c.type === 'Block') .map(c => c.value.trim().match(/^@import\([\'"`]*([- \./\w*]+)['"`]\)* *(@lazy)? *(?:@chunk\(['"`]*([\w-]+)['"`]*\))? *(@lazy)?$/)) .filter(c => !!c) .map((c: RegExpMatchArray) => ({ literal: c[1], lazy: !!(c[2] || c[4]), chunk: c[3] })) if (!commentsAndLiterals.length && !commentOnlyImports.length) { this.callback(undefined, source, sourceMap) return } const allResources = [...commentsAndLiterals, ...commentOnlyImports] try { let resourceData = await addBundleLoader(allResources) if (query.enableGlobbing) { resourceData = await expandAllRequiresForGlob(resourceData, this) } else { resourceData = resourceData.filter(r => !r.literal.includes(`*`)) } log(`Adding resources to ${this.resourcePath}: ${resourceData.map(r => r.literal).join(', ')}`) const requireStrings = await getRequireStrings(resourceData, query.addLoadersCallback, this, query.alwaysUseCommentBundles) const inject = requireStrings.map(wrapInRequireInclude).join('\n') appendCodeAndCallback(this, source, inject, sourceMap) } catch (e) { log(e) this.emitError(e.message) this.callback(undefined, source, sourceMap) } }