2013-01-31 01:49:25 +08:00
/ *
MIT License http : //www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @ sokra
* /
2018-07-30 23:08:51 +08:00
2017-05-11 03:36:20 +08:00
"use strict" ;
2018-02-11 12:27:09 +08:00
const asyncLib = require ( "neo-async" ) ;
2018-06-26 14:27:44 +08:00
const { AsyncSeriesWaterfallHook , SyncWaterfallHook } = require ( "tapable" ) ;
2017-05-11 03:36:20 +08:00
const ContextModule = require ( "./ContextModule" ) ;
2019-01-05 02:17:37 +08:00
const ModuleFactory = require ( "./ModuleFactory" ) ;
2017-05-11 03:36:20 +08:00
const ContextElementDependency = require ( "./dependencies/ContextElementDependency" ) ;
2021-04-23 19:51:39 +08:00
const LazySet = require ( "./util/LazySet" ) ;
2020-06-09 06:32:24 +08:00
const { cachedSetProperty } = require ( "./util/cleverMerge" ) ;
2020-07-14 18:02:32 +08:00
const { createFakeHook } = require ( "./util/deprecation" ) ;
2019-06-11 19:09:42 +08:00
const { join } = require ( "./util/fs" ) ;
2017-05-11 03:36:20 +08:00
2020-06-10 19:31:01 +08:00
/** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */
2018-07-05 13:20:24 +08:00
/** @typedef {import("./Module")} Module */
2019-01-05 02:17:37 +08:00
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
2020-06-09 06:32:24 +08:00
/** @typedef {import("./ResolverFactory")} ResolverFactory */
2019-01-05 02:17:37 +08:00
/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
2020-07-14 18:02:32 +08:00
/** @template T @typedef {import("./util/deprecation").FakeHook<T>} FakeHook<T> */
2020-06-10 19:31:01 +08:00
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
2018-07-05 13:20:24 +08:00
2020-06-17 02:01:06 +08:00
const EMPTY _RESOLVE _OPTIONS = { } ;
2020-11-29 03:04:11 +08:00
module . exports = class ContextModuleFactory extends ModuleFactory {
2020-06-09 06:32:24 +08:00
/ * *
* @ param { ResolverFactory } resolverFactory resolverFactory
* /
2017-11-17 21:26:23 +08:00
constructor ( resolverFactory ) {
2019-01-05 02:17:37 +08:00
super ( ) ;
2020-07-14 18:02:32 +08:00
/** @type {AsyncSeriesWaterfallHook<[TODO[], ContextModuleOptions]>} */
const alternativeRequests = new AsyncSeriesWaterfallHook ( [
"modules" ,
"options"
] ) ;
2018-07-30 20:25:40 +08:00
this . hooks = Object . freeze ( {
2018-12-09 19:54:17 +08:00
/** @type {AsyncSeriesWaterfallHook<[TODO]>} */
2017-11-28 16:54:24 +08:00
beforeResolve : new AsyncSeriesWaterfallHook ( [ "data" ] ) ,
2018-12-09 19:54:17 +08:00
/** @type {AsyncSeriesWaterfallHook<[TODO]>} */
2017-11-28 16:54:24 +08:00
afterResolve : new AsyncSeriesWaterfallHook ( [ "data" ] ) ,
2018-12-09 19:54:17 +08:00
/** @type {SyncWaterfallHook<[string[]]>} */
2017-11-28 16:54:24 +08:00
contextModuleFiles : new SyncWaterfallHook ( [ "files" ] ) ,
2020-07-14 18:02:32 +08:00
/** @type {FakeHook<Pick<AsyncSeriesWaterfallHook<[TODO[]]>, "tap" | "tapAsync" | "tapPromise" | "name">>} */
alternatives : createFakeHook (
{
name : "alternatives" ,
/** @type {AsyncSeriesWaterfallHook<[TODO[]]>["intercept"]} */
intercept : interceptor => {
throw new Error (
"Intercepting fake hook ContextModuleFactory.hooks.alternatives is not possible, use ContextModuleFactory.hooks.alternativeRequests instead"
) ;
} ,
/** @type {AsyncSeriesWaterfallHook<[TODO[]]>["tap"]} */
tap : ( options , fn ) => {
alternativeRequests . tap ( options , fn ) ;
} ,
/** @type {AsyncSeriesWaterfallHook<[TODO[]]>["tapAsync"]} */
tapAsync : ( options , fn ) => {
alternativeRequests . tapAsync ( options , ( items , _options , callback ) =>
fn ( items , callback )
) ;
} ,
/** @type {AsyncSeriesWaterfallHook<[TODO[]]>["tapPromise"]} */
tapPromise : ( options , fn ) => {
alternativeRequests . tapPromise ( options , fn ) ;
}
} ,
"ContextModuleFactory.hooks.alternatives has deprecated in favor of ContextModuleFactory.hooks.alternativeRequests with an additional options argument." ,
"DEP_WEBPACK_CONTEXT_MODULE_FACTORY_ALTERNATIVES"
) ,
alternativeRequests
2018-07-30 20:25:40 +08:00
} ) ;
2017-11-17 21:26:23 +08:00
this . resolverFactory = resolverFactory ;
2017-05-11 03:36:20 +08:00
}
2019-01-05 02:17:37 +08:00
/ * *
* @ param { ModuleFactoryCreateData } data data object
* @ param { function ( Error = , ModuleFactoryResult = ) : void } callback callback
* @ returns { void }
* /
2017-05-11 03:36:20 +08:00
create ( data , callback ) {
const context = data . context ;
const dependencies = data . dependencies ;
2017-11-17 21:26:23 +08:00
const resolveOptions = data . resolveOptions ;
2019-01-05 02:17:37 +08:00
const dependency = /** @type {ContextDependency} */ ( dependencies [ 0 ] ) ;
2021-04-23 19:51:39 +08:00
const fileDependencies = new LazySet ( ) ;
const missingDependencies = new LazySet ( ) ;
const contextDependencies = new LazySet ( ) ;
2018-02-25 09:00:20 +08:00
this . hooks . beforeResolve . callAsync (
2019-06-19 19:16:05 +08:00
{
context : context ,
dependencies : dependencies ,
resolveOptions ,
fileDependencies ,
missingDependencies ,
contextDependencies ,
... dependency . options
} ,
2018-02-25 09:00:20 +08:00
( err , beforeResolveResult ) => {
2019-07-05 06:41:30 +08:00
if ( err ) {
return callback ( err , {
fileDependencies ,
missingDependencies ,
contextDependencies
} ) ;
}
2018-02-25 09:00:20 +08:00
// Ignored
2019-07-05 06:41:30 +08:00
if ( ! beforeResolveResult ) {
return callback ( null , {
fileDependencies ,
missingDependencies ,
contextDependencies
} ) ;
}
2018-02-25 09:00:20 +08:00
const context = beforeResolveResult . context ;
const request = beforeResolveResult . request ;
const resolveOptions = beforeResolveResult . resolveOptions ;
let loaders ,
resource ,
loadersPrefix = "" ;
const idx = request . lastIndexOf ( "!" ) ;
if ( idx >= 0 ) {
2018-05-29 20:50:40 +08:00
let loadersRequest = request . substr ( 0 , idx + 1 ) ;
2018-02-25 09:00:20 +08:00
let i ;
2018-05-29 20:50:40 +08:00
for (
i = 0 ;
i < loadersRequest . length && loadersRequest [ i ] === "!" ;
i ++
) {
2018-02-25 09:00:20 +08:00
loadersPrefix += "!" ;
}
2018-05-29 20:50:40 +08:00
loadersRequest = loadersRequest
2018-02-25 09:00:20 +08:00
. substr ( i )
. replace ( /!+$/ , "" )
. replace ( /!!+/g , "!" ) ;
2018-05-29 20:50:40 +08:00
if ( loadersRequest === "" ) {
loaders = [ ] ;
} else {
loaders = loadersRequest . split ( "!" ) ;
}
2018-02-25 09:00:20 +08:00
resource = request . substr ( idx + 1 ) ;
} else {
loaders = [ ] ;
resource = request ;
2017-05-11 03:36:20 +08:00
}
2018-02-25 09:00:20 +08:00
const contextResolver = this . resolverFactory . get (
"context" ,
2020-06-17 02:01:06 +08:00
dependencies . length > 0
? cachedSetProperty (
resolveOptions || EMPTY _RESOLVE _OPTIONS ,
"dependencyType" ,
dependencies [ 0 ] . category
)
: resolveOptions
2018-02-25 09:00:20 +08:00
) ;
2020-06-01 20:13:17 +08:00
const loaderResolver = this . resolverFactory . get ( "loader" ) ;
2018-02-25 09:00:20 +08:00
asyncLib . parallel (
[
callback => {
2022-02-21 16:58:44 +08:00
const results = [ ] ;
const yield _ = obj => results . push ( obj ) ;
2018-02-25 09:00:20 +08:00
contextResolver . resolve (
{ } ,
context ,
resource ,
2019-07-05 06:41:30 +08:00
{
fileDependencies ,
missingDependencies ,
2022-02-21 16:58:44 +08:00
contextDependencies ,
yield : yield _
2019-07-05 06:41:30 +08:00
} ,
2022-02-21 16:58:44 +08:00
err => {
2018-02-25 09:00:20 +08:00
if ( err ) return callback ( err ) ;
2022-02-21 16:58:44 +08:00
callback ( null , results ) ;
2018-02-25 09:00:20 +08:00
}
) ;
} ,
callback => {
asyncLib . map (
loaders ,
( loader , callback ) => {
loaderResolver . resolve (
{ } ,
context ,
loader ,
2019-07-05 06:41:30 +08:00
{
fileDependencies ,
missingDependencies ,
contextDependencies
} ,
2018-02-25 09:00:20 +08:00
( err , result ) => {
if ( err ) return callback ( err ) ;
callback ( null , result ) ;
}
) ;
} ,
callback
) ;
}
] ,
( err , result ) => {
2019-07-05 06:41:30 +08:00
if ( err ) {
return callback ( err , {
fileDependencies ,
missingDependencies ,
contextDependencies
} ) ;
}
2022-02-21 18:25:30 +08:00
let [ contextResult , loaderResult ] = result ;
if ( contextResult . length > 1 ) {
const first = contextResult [ 0 ] ;
contextResult = contextResult . filter ( r => r . path ) ;
if ( contextResult . length === 0 ) contextResult . push ( first ) ;
}
2018-02-25 09:00:20 +08:00
this . hooks . afterResolve . callAsync (
2019-06-19 19:16:05 +08:00
{
addon :
loadersPrefix +
2022-02-21 16:58:44 +08:00
loaderResult . join ( "!" ) +
( loaderResult . length > 0 ? "!" : "" ) ,
resource :
contextResult . length > 1
? contextResult . map ( r => r . path )
: contextResult [ 0 ] . path ,
2019-06-19 19:16:05 +08:00
resolveDependencies : this . resolveDependencies . bind ( this ) ,
2022-02-21 16:58:44 +08:00
resourceQuery : contextResult [ 0 ] . query ,
resourceFragment : contextResult [ 0 ] . fragment ,
2019-06-19 19:16:05 +08:00
... beforeResolveResult
} ,
2018-02-25 09:00:20 +08:00
( err , result ) => {
2019-07-05 06:41:30 +08:00
if ( err ) {
return callback ( err , {
fileDependencies ,
missingDependencies ,
contextDependencies
} ) ;
}
2018-02-25 09:00:20 +08:00
// Ignored
2019-07-05 06:41:30 +08:00
if ( ! result ) {
return callback ( null , {
fileDependencies ,
missingDependencies ,
contextDependencies
} ) ;
}
2018-02-25 09:00:20 +08:00
2019-01-05 02:17:37 +08:00
return callback ( null , {
module : new ContextModule ( result . resolveDependencies , result ) ,
fileDependencies ,
missingDependencies ,
contextDependencies
} ) ;
2018-02-25 09:00:20 +08:00
}
) ;
}
) ;
}
) ;
2017-05-11 03:36:20 +08:00
}
2013-02-04 19:34:20 +08:00
2020-06-10 19:31:01 +08:00
/ * *
* @ param { InputFileSystem } fs file system
* @ param { ContextModuleOptions } options options
* @ param { ResolveDependenciesCallback } callback callback function
* @ returns { void }
* /
2017-10-14 07:51:01 +08:00
resolveDependencies ( fs , options , callback ) {
2017-09-25 19:27:51 +08:00
const cmf = this ;
2020-06-10 19:31:01 +08:00
const {
resource ,
resourceQuery ,
2020-07-03 23:03:15 +08:00
resourceFragment ,
2020-06-10 19:31:01 +08:00
recursive ,
regExp ,
include ,
exclude ,
2020-06-18 05:03:02 +08:00
referencedExports ,
2021-05-31 19:44:09 +08:00
category ,
typePrefix
2020-06-10 19:31:01 +08:00
} = options ;
2018-02-25 09:00:20 +08:00
if ( ! regExp || ! resource ) return callback ( null , [ ] ) ;
2017-11-16 14:20:50 +08:00
2022-02-21 16:58:44 +08:00
let severalContexts = false ;
const addDirectoryChecked = ( ctx , directory , visited , callback ) => {
2020-09-03 00:29:45 +08:00
fs . realpath ( directory , ( err , realPath ) => {
if ( err ) return callback ( err ) ;
if ( visited . has ( realPath ) ) return callback ( null , [ ] ) ;
let recursionStack ;
addDirectory (
2022-02-21 16:58:44 +08:00
ctx ,
2020-09-03 00:29:45 +08:00
directory ,
2022-02-21 16:58:44 +08:00
( _ , dir , callback ) => {
2020-09-03 00:29:45 +08:00
if ( recursionStack === undefined ) {
recursionStack = new Set ( visited ) ;
recursionStack . add ( realPath ) ;
}
2022-02-21 16:58:44 +08:00
addDirectoryChecked ( ctx , dir , recursionStack , callback ) ;
2020-09-03 00:29:45 +08:00
} ,
callback
) ;
} ) ;
} ;
2022-02-21 16:58:44 +08:00
const addDirectory = ( ctx , directory , addSubDirectory , callback ) => {
2017-08-11 13:52:25 +08:00
fs . readdir ( directory , ( err , files ) => {
2018-02-25 09:00:20 +08:00
if ( err ) return callback ( err ) ;
2021-01-13 07:09:19 +08:00
const processedFiles = cmf . hooks . contextModuleFiles . call (
/** @type {string[]} */ ( files ) . map ( file => file . normalize ( "NFC" ) )
) ;
if ( ! processedFiles || processedFiles . length === 0 )
return callback ( null , [ ] ) ;
2018-02-25 09:00:20 +08:00
asyncLib . map (
2021-01-13 07:09:19 +08:00
processedFiles . filter ( p => p . indexOf ( "." ) !== 0 ) ,
2018-02-26 10:43:37 +08:00
( segment , callback ) => {
2019-06-11 19:09:42 +08:00
const subResource = join ( fs , directory , segment ) ;
2018-02-25 09:00:20 +08:00
if ( ! exclude || ! subResource . match ( exclude ) ) {
fs . stat ( subResource , ( err , stat ) => {
if ( err ) {
if ( err . code === "ENOENT" ) {
// ENOENT is ok here because the file may have been deleted between
// the readdir and stat calls.
return callback ( ) ;
} else {
return callback ( err ) ;
}
2017-10-14 07:51:01 +08:00
}
2013-02-04 19:34:20 +08:00
2018-02-25 09:00:20 +08:00
if ( stat . isDirectory ( ) ) {
if ( ! recursive ) return callback ( ) ;
2022-02-21 16:58:44 +08:00
addSubDirectory ( ctx , subResource , callback ) ;
2018-02-25 09:00:20 +08:00
} else if (
stat . isFile ( ) &&
( ! include || subResource . match ( include ) )
) {
const obj = {
2022-02-21 16:58:44 +08:00
context : ctx ,
2018-02-25 09:00:20 +08:00
request :
2022-02-21 16:58:44 +08:00
"." + subResource . substr ( ctx . length ) . replace ( /\\/g , "/" )
2018-02-25 09:00:20 +08:00
} ;
2020-07-14 18:02:32 +08:00
this . hooks . alternativeRequests . callAsync (
2018-02-25 09:00:20 +08:00
[ obj ] ,
2020-07-14 18:02:32 +08:00
options ,
2018-02-25 09:00:20 +08:00
( err , alternatives ) => {
if ( err ) return callback ( err ) ;
alternatives = alternatives
. filter ( obj => regExp . test ( obj . request ) )
. map ( obj => {
2022-02-21 16:58:44 +08:00
const request = severalContexts
? join ( fs , obj . context , obj . request )
: obj . request ;
2018-02-25 09:00:20 +08:00
const dep = new ContextElementDependency (
2022-02-21 16:58:44 +08:00
request + resourceQuery + resourceFragment ,
2020-06-10 19:31:01 +08:00
obj . request ,
2021-05-31 19:44:09 +08:00
typePrefix ,
2020-06-18 05:03:02 +08:00
category ,
2020-06-10 19:31:01 +08:00
referencedExports
2018-02-25 09:00:20 +08:00
) ;
dep . optional = true ;
return dep ;
} ) ;
callback ( null , alternatives ) ;
}
) ;
2018-05-29 20:50:40 +08:00
} else {
callback ( ) ;
}
2018-02-25 09:00:20 +08:00
} ) ;
2018-05-29 20:50:40 +08:00
} else {
callback ( ) ;
}
2018-02-25 09:00:20 +08:00
} ,
( err , result ) => {
if ( err ) return callback ( err ) ;
if ( ! result ) return callback ( null , [ ] ) ;
2020-03-13 00:51:26 +08:00
const flattenedResult = [ ] ;
2020-01-17 04:22:05 +08:00
for ( const item of result ) {
2020-03-13 00:51:26 +08:00
if ( item ) flattenedResult . push ( ... item ) ;
2020-01-17 04:22:05 +08:00
}
2020-03-13 00:51:26 +08:00
callback ( null , flattenedResult ) ;
2018-02-25 09:00:20 +08:00
}
) ;
2017-08-11 13:52:25 +08:00
} ) ;
2017-11-08 18:32:05 +08:00
} ;
2022-02-21 16:58:44 +08:00
const addSubDirectory = ( ctx , dir , callback ) =>
addDirectory ( ctx , dir , addSubDirectory , callback ) ;
const visitResource = ( resource , callback ) => {
if ( typeof fs . realpath === "function" ) {
addDirectoryChecked ( resource , resource , new Set ( ) , callback ) ;
} else {
addDirectory ( resource , resource , addSubDirectory , callback ) ;
}
} ;
if ( typeof resource === "string" ) {
visitResource ( resource , callback ) ;
2020-09-03 00:29:45 +08:00
} else {
2022-02-21 16:58:44 +08:00
severalContexts = true ;
asyncLib . map ( resource , visitResource , ( err , result ) => {
if ( err ) return callback ( err ) ;
// result dependencies should have unique userRequest
// ordered by resolve result
const temp = new Set ( ) ;
const res = [ ] ;
for ( let i = 0 ; i < result . length ; i ++ ) {
const inner = result [ i ] ;
for ( const el of inner ) {
if ( temp . has ( el . userRequest ) ) continue ;
res . push ( el ) ;
temp . add ( el . userRequest ) ;
}
}
callback ( null , res ) ;
} ) ;
2020-09-03 00:29:45 +08:00
}
2017-05-11 03:36:20 +08:00
}
2013-01-31 01:49:25 +08:00
} ;