2020-02-20 03:25:49 +08:00
/ *
MIT License http : //www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @ sokra
* /
"use strict" ;
const { ConcatSource } = require ( "webpack-sources" ) ;
2021-01-06 22:01:59 +08:00
const Template = require ( "../Template" ) ;
2020-02-20 03:25:49 +08:00
const propertyAccess = require ( "../util/propertyAccess" ) ;
const AbstractLibraryPlugin = require ( "./AbstractLibraryPlugin" ) ;
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */
/** @typedef {import("../util/Hash")} Hash */
/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext<T>} LibraryContext<T> */
2021-01-06 22:01:59 +08:00
const KEYWORD _REGEX = /^(await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|super|switch|static|this|throw|try|true|typeof|var|void|while|with|yield)$/ ;
const IDENTIFIER _REGEX = /^[\p{L}\p{Nl}$_][\p{L}\p{Nl}$\p{Mn}\p{Mc}\p{Nd}\p{Pc}]*$/iu ;
/ * *
* Validates the library name by checking for keywords and valid characters
* @ param { string } name name to be validated
* @ returns { boolean } true , when valid
* /
const isNameValid = name => {
return ! KEYWORD _REGEX . test ( name ) && IDENTIFIER _REGEX . test ( name ) ;
2020-11-06 01:33:19 +08:00
} ;
2020-02-20 03:25:49 +08:00
/ * *
* @ param { string [ ] } accessor variable plus properties
* @ param { number } existingLength items of accessor that are existing already
* @ param { boolean = } initLast if the last property should also be initialized to an object
* @ returns { string } code to access the accessor while initializing
* /
const accessWithInit = ( accessor , existingLength , initLast = false ) => {
// This generates for [a, b, c, d]:
// (((a = typeof a === "undefined" ? {} : a).b = a.b || {}).c = a.b.c || {}).d
const base = accessor [ 0 ] ;
2020-02-26 23:13:38 +08:00
if ( accessor . length === 1 && ! initLast ) return base ;
2020-02-20 03:25:49 +08:00
let current =
2020-02-26 23:13:38 +08:00
existingLength > 0
2020-02-20 03:25:49 +08:00
? base
: ` ( ${ base } = typeof ${ base } === "undefined" ? {} : ${ base } ) ` ;
2020-02-26 23:13:38 +08:00
// i is the current position in accessor that has been printed
let i = 1 ;
// all properties printed so far (excluding base)
let propsSoFar ;
// if there is existingLength, print all properties until this position as property access
if ( existingLength > i ) {
propsSoFar = accessor . slice ( 1 , existingLength ) ;
i = existingLength ;
current += propertyAccess ( propsSoFar ) ;
} else {
propsSoFar = [ ] ;
}
2020-03-10 09:59:46 +08:00
// all remaining properties (except the last one when initLast is not set)
2020-02-26 23:13:38 +08:00
// should be printed as initializer
const initUntil = initLast ? accessor . length : accessor . length - 1 ;
for ( ; i < initUntil ; i ++ ) {
2020-02-20 03:25:49 +08:00
const prop = accessor [ i ] ;
propsSoFar . push ( prop ) ;
current = ` ( ${ current } ${ propertyAccess ( [ prop ] ) } = ${ base } ${ propertyAccess (
propsSoFar
) } || { } ) ` ;
}
2020-02-26 23:13:38 +08:00
// print the last property as property access if not yet printed
if ( i < accessor . length )
current = ` ${ current } ${ propertyAccess ( [ accessor [ accessor . length - 1 ] ] ) } ` ;
return current ;
2020-02-20 03:25:49 +08:00
} ;
/ * *
* @ typedef { Object } AssignLibraryPluginOptions
* @ property { LibraryType } type
* @ property { string [ ] | "global" } prefix name prefix
* @ property { string | false } declare declare name as variable
* @ property { "error" | "copy" | "assign" } unnamed behavior for unnamed library name
* /
/ * *
* @ typedef { Object } AssignLibraryPluginParsed
* @ property { string | string [ ] } name
* /
/ * *
* @ typedef { AssignLibraryPluginParsed } T
* @ extends { AbstractLibraryPlugin < AssignLibraryPluginParsed > }
* /
class AssignLibraryPlugin extends AbstractLibraryPlugin {
/ * *
* @ param { AssignLibraryPluginOptions } options the plugin options
* /
constructor ( options ) {
super ( {
pluginName : "AssignLibraryPlugin" ,
type : options . type
} ) ;
this . prefix = options . prefix ;
this . declare = options . declare ;
this . unnamed = options . unnamed ;
}
/ * *
* @ param { LibraryOptions } library normalized library option
* @ returns { T | false } preprocess as needed by overriding
* /
parseOptions ( library ) {
2020-02-27 00:20:50 +08:00
const { name } = library ;
2020-02-20 03:25:49 +08:00
if ( this . unnamed === "error" ) {
if ( typeof name !== "string" && ! Array . isArray ( name ) ) {
2020-11-03 19:41:32 +08:00
throw new Error ( "output.library.name must be a string or string array" ) ;
2020-02-20 03:25:49 +08:00
}
} else {
if ( name && typeof name !== "string" && ! Array . isArray ( name ) ) {
2020-11-03 19:41:32 +08:00
throw new Error (
"output.library.name must be a string, string array or unset"
) ;
2020-02-20 03:25:49 +08:00
}
}
return {
name : /** @type {string|string[]=} */ ( name )
} ;
}
/ * *
* @ param { Source } source source
* @ param { RenderContext } renderContext render context
* @ param { LibraryContext < T > } libraryContext context
* @ returns { Source } source with library export
* /
render ( source , { chunkGraph , moduleGraph , chunk } , { options , compilation } ) {
const prefix =
this . prefix === "global"
? [ compilation . outputOptions . globalObject ]
: this . prefix ;
const fullName = options . name ? prefix . concat ( options . name ) : prefix ;
const fullNameResolved = fullName . map ( n =>
compilation . getPath ( n , {
chunk
} )
) ;
const result = new ConcatSource ( ) ;
if ( this . declare ) {
const base = fullNameResolved [ 0 ] ;
2020-11-06 01:33:19 +08:00
if ( ! isNameValid ( base ) ) {
throw new Error (
2021-01-06 22:01:59 +08:00
` Library name base ( ${ base } ) must be a valid identifier when using a var declaring library type. Either use a valid identifier (e. g. ${ Template . toIdentifier (
base
) } ) or use a different library type ( e . g . 'type: "global"' , which assign a property on the global scope instead of declaring a variable ) . Common configuration options that specific library names are 'output.library[.name]' , 'entry.xyz.library[.name]' , 'ModuleFederationPlugin.name' and 'ModuleFederationPlugin.library[.name]' . `
2020-11-06 01:33:19 +08:00
) ;
}
2020-02-20 03:25:49 +08:00
result . add ( ` ${ this . declare } ${ base } ; ` ) ;
}
if ( ! options . name && this . unnamed === "copy" ) {
result . add (
` (function(e, a) { for(var i in a) e[i] = a[i]; if(a.__esModule) Object.defineProperty(e, "__esModule", { value: true }); }( ${ accessWithInit (
fullNameResolved ,
prefix . length ,
true
) } , \ n `
) ;
result . add ( source ) ;
result . add ( "\n))" ) ;
} else {
result . add (
` ${ accessWithInit ( fullNameResolved , prefix . length , false ) } = \n `
) ;
result . add ( source ) ;
}
return result ;
}
/ * *
* @ param { Chunk } chunk the chunk
* @ param { Hash } hash hash
* @ param { ChunkHashContext } chunkHashContext chunk hash context
* @ param { LibraryContext < T > } libraryContext context
* @ returns { void }
* /
chunkHash ( chunk , hash , chunkHashContext , { options , compilation } ) {
hash . update ( "AssignLibraryPlugin" ) ;
const prefix =
this . prefix === "global"
? [ compilation . outputOptions . globalObject ]
: this . prefix ;
const fullName = options . name ? prefix . concat ( options . name ) : prefix ;
const fullNameResolved = fullName . map ( n =>
compilation . getPath ( n , {
chunk
} )
) ;
if ( ! options . name && this . unnamed === "copy" ) {
hash . update ( "copy" ) ;
}
if ( this . declare ) {
hash . update ( this . declare ) ;
}
hash . update ( fullNameResolved . join ( "." ) ) ;
}
}
module . exports = AssignLibraryPlugin ;