import * as qs from 'querystring' ; import webpack = require ( 'webpack' ) ; import type { VueLoaderOptions } from './' ; const RuleSet = require ( 'webpack/lib/RuleSet' ) ; const id = 'vue-loader-plugin' ; const NS = 'vue-loader' ; class VueLoaderPlugin implements webpack . Plugin { static NS = NS ; apply ( compiler ) { compiler . hooks . compilation . tap ( id , ( compilation ) => { compilation . hooks . normalModuleLoader . tap ( id , ( loaderContext ) => { loaderContext [ NS ] = true ; } ) ; } ) ; const rawRules = compiler . options . module ! . rules ; const rules = new RuleSet ( rawRules ) . rules as webpack . RuleSetRule [ ] ; let vueRuleIndex = rawRules . findIndex ( createMatcher ( `foo.vue` ) ) ; if ( vueRuleIndex < 0 ) { vueRuleIndex = rawRules . findIndex ( createMatcher ( `foo.vue.html` ) ) ; } const vueRule = rules [ vueRuleIndex ] ; if ( ! vueRule ) { throw new Error ( `[VueLoaderPlugin Error] No matching rule for .vue files found.\n` + `Make sure there is at least one root-level rule that matches .vue or .vue.html files.` ) ; } if ( vueRule . oneOf ) { throw new Error ( `[VueLoaderPlugin Error] vue-loader currently does not support vue rules with oneOf.` ) ; } const vueUse = vueRule . use as webpack . RuleSetLoader [ ] ; const vueLoaderUseIndex = vueUse . findIndex ( ( u ) => { return /^vue-loader|(\/|\\|@)vue-loader/ . test ( u . loader || '' ) ; } ) ; if ( vueLoaderUseIndex < 0 ) { throw new Error ( `[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` + `Make sure the rule matching .vue files include vue-loader in its use.` ) ; } const vueLoaderUse = vueUse [ vueLoaderUseIndex ] ; const vueLoaderOptions = ( vueLoaderUse . options = vueLoaderUse . options || { } ) as VueLoaderOptions ; const clonedRules = rules . filter ( ( r ) => r !== vueRule ) . map ( cloneRule ) ; const templateCompilerRule = { loader : require . resolve ( './templateLoader' ) , resourceQuery : ( query ) => { const parsed = qs . parse ( query . slice ( 1 ) ) ; return parsed . vue != null && parsed . type === 'template' ; } , options : vueLoaderOptions , } ; const matchesJS = createMatcher ( `test.js` ) ; const matchesTS = createMatcher ( `test.ts` ) ; const jsRulesForRenderFn = rules . filter ( ( r ) => r !== vueRule && ( matchesJS ( r ) || matchesTS ( r ) ) ) . map ( cloneRuleForRenderFn ) ; const pitcher = { loader : require . resolve ( './pitcher' ) , resourceQuery : ( query ) => { const parsed = qs . parse ( query . slice ( 1 ) ) ; return parsed . vue != null ; } , } ; compiler . options . module ! . rules = [ pitcher , ... jsRulesForRenderFn , templateCompilerRule , ... clonedRules , ... rules , ] ; } } function createMatcher ( fakeFile ) { return ( rule ) => { const clone = Object . assign ( { } , rule ) ; delete clone . include ; const normalized = RuleSet . normalizeRule ( clone , { } , '' ) ; return ! rule . enforce && normalized . resource && normalized . resource ( fakeFile ) ; } ; } function cloneRule ( rule ) { const resource = rule . resource as Function ; const resourceQuery = rule . resourceQuery as Function ; let currentResource ; const res = { ... rule , resource : ( resource ) => { currentResource = resource ; return true ; } , resourceQuery : ( query ) => { const parsed = qs . parse ( query . slice ( 1 ) ) ; if ( parsed . vue == null ) { return false ; } if ( resource && parsed . lang == null ) { return false ; } const fakeResourcePath = `${ currentResource }.${ parsed . lang }` ; if ( resource && ! resource ( fakeResourcePath ) ) { return false ; } if ( resourceQuery && ! resourceQuery ( query ) ) { return false ; } return true ; } , } ; if ( rule . rules ) { res . rules = rule . rules . map ( cloneRule ) ; } if ( rule . oneOf ) { res . oneOf = rule . oneOf . map ( cloneRule ) ; } return res ; } function cloneRuleForRenderFn ( rule ) { const resource = rule . resource as Function ; const resourceQuery = rule . resourceQuery as Function ; let currentResource ; const res = { ... rule , resource : ( resource ) => { currentResource = resource ; return true ; } , resourceQuery : ( query ) => { const parsed = qs . parse ( query . slice ( 1 ) ) ; if ( parsed . vue == null || parsed . type !== 'template' ) { return false ; } const fakeResourcePath = `${ currentResource }.${ parsed . ts ? `ts` : `js` }` ; if ( resource && ! resource ( fakeResourcePath ) ) { return false ; } if ( resourceQuery && ! resourceQuery ( query ) ) { return false ; } return true ; } , } ; if ( rule . rules ) { res . rules = rule . rules . map ( cloneRuleForRenderFn ) ; } if ( rule . oneOf ) { res . oneOf = rule . oneOf . map ( cloneRuleForRenderFn ) ; } return res ; } export default VueLoaderPlugin ;