next-csrf
Version:
CSRF mitigation library for Next.js
56 lines • 9.5 kB
JavaScript
;exports.__esModule=true;exports.NextEsmPlugin=void 0;/**
* MIT License
*
* Copyright (c) 2018 Prateek Bhatnagar
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/ /**
* Webpack plugin for NextJs that runs a child compiler to generate a second (modern) JS bundle
*
* @author: Janicklas Ralph (https://github.com/janickals-ralph)
*
* Original source from which this was built upon - https://github.com/prateekbh/babel-esm-plugin
*/const SingleEntryPlugin=require('webpack/lib/SingleEntryPlugin');const MultiEntryPlugin=require('webpack/lib/MultiEntryPlugin');const JsonpTemplatePlugin=require('webpack/lib/web/JsonpTemplatePlugin');const SplitChunksPlugin=require('webpack/lib/optimize/SplitChunksPlugin');const RuntimeChunkPlugin=require('webpack/lib/optimize/RuntimeChunkPlugin');const PLUGIN_NAME='NextEsmPlugin';// Matches all variations of preset-env as a filepath.
// Example: /project/foo/node_modules/babel-preset-react-app/dependencies.js
const IS_PRESET_ENV=/(^|[\\/])(babel-preset-react-app\/dependencies(\.js)?|@babel\/(preset-)?env)([\\/]|$)/;// Matches Babel preset paths that support the useBuiltIns option
const PRESETS_WITH_USEBUILTINS=/(^|[\\/])(@babel\/(preset-)?react)([\\/]|$)/;// Matches Babel plugin paths that support the useBuiltIns option
const PLUGINS_WITH_USEBUILTINS=/(^|[\\/])(@babel\/(plugin-)?transform-react-jsx)([\\/]|$)/;class NextEsmPlugin{constructor(options){this.options=void 0;this.options=Object.assign({excludedPlugins:[],additionalPlugins:[]},options);}apply(compiler){compiler.hooks.make.tapAsync(PLUGIN_NAME,(compilation,callback)=>{this.runBuild(compiler,compilation).then(callback);});}getLoaders(rules,predicate){const results=[];for(let rule of rules){if(Array.isArray(rule.use)){const matches=rule.use.filter(r=>r.loader&&predicate(r.loader));if(matches.length>0){results.push(...matches);}}const ruleUse=rule.use;let ruleLoader=rule.loader;if(typeof ruleLoader==='object'&&'loader'in ruleLoader){ruleLoader=ruleLoader.loader;}if((ruleUse===null||ruleUse===void 0?void 0:ruleUse.loader)&&predicate(ruleUse.loader)||ruleLoader&&predicate(ruleLoader)){results.push(ruleUse||rule);}}return results;}updateOptions(childCompiler){if(!childCompiler.options.module){throw new Error('Webpack.options.module not found!');}let babelLoader=this.getLoaders(childCompiler.options.module.rules,loader=>loader.includes('next-babel-loader'))[0];if(!babelLoader){throw new Error('Babel-loader config not found!');}babelLoader.options=Object.assign({},babelLoader.options,{isModern:true});if(typeof babelLoader.options!=='string'){this.ensureModernBabelOptions(babelLoader.options);}const additionalBabelLoaders=this.getLoaders(childCompiler.options.module.rules,loader=>/(^|[\\/])babel-loader([\\/]|$)/.test(loader));for(const loader of additionalBabelLoaders){// @TODO support string options?
if(!loader.options||typeof loader.options==='string')continue;this.ensureModernBabelOptions(loader.options);}}ensureModernBabelOptions(options){// find and remove known ES2017-to-ES5 transforms
if(options.presets){options.presets=options.presets.reduce((presets,preset)=>{const name=Array.isArray(preset)?preset[0]:preset;const opts=Object.assign({},Array.isArray(preset)&&preset[1]||{});if(PRESETS_WITH_USEBUILTINS.test(name)){opts.useBuiltIns=true;}if(IS_PRESET_ENV.test(name)){presets.push([require.resolve('@babel/preset-modules'),{loose:true}]);}else{presets.push([name,opts]);}return presets;},[]);}if(options.plugins){options.plugins=options.plugins.map(plugin=>{const name=Array.isArray(plugin)?plugin[0]:plugin;const opts=Object.assign({},Array.isArray(plugin)&&plugin[1]||{});if(PLUGINS_WITH_USEBUILTINS.test(name)){opts.useBuiltIns=true;}return[name,opts];});}}updateAssets(compilation,childCompilation){compilation.assets=Object.assign(childCompilation.assets,compilation.assets);compilation.namedChunkGroups=Object.assign(childCompilation.namedChunkGroups,compilation.namedChunkGroups);const unnamedChunks=[];const childChunkFileMap=childCompilation.chunks.reduce((chunkMap,chunk)=>{// Dynamic chunks may not have a name. It'll be null in such cases
if(chunk.name===null){unnamedChunks.push(chunk);}else{chunkMap[chunk.name]=chunk;}return chunkMap;},{});// Merge chunks - merge the files of chunks with the same name
compilation.chunks.forEach(chunk=>{const childChunk=childChunkFileMap[chunk.name];// Do not merge null named chunks since they are different
if(chunk.name!==null&&(childChunk===null||childChunk===void 0?void 0:childChunk.files)){delete childChunkFileMap[chunk.name];chunk.files.push(...childChunk.files.filter(v=>!chunk.files.includes(v)));}});// Add modern only chunks into the main compilation
compilation.chunks.push(...Object.values(childChunkFileMap),...unnamedChunks);// Place modern only (unmerged) chunks inside the right entry point
compilation.entrypoints.forEach((entryPoint,entryPointName)=>{const childEntryPoint=childCompilation.entrypoints.get(entryPointName);childEntryPoint.chunks.forEach(chunk=>{if(// Add null named dynamic chunks since they weren't merged
chunk.name===null||childChunkFileMap.hasOwnProperty(chunk.name)){entryPoint.chunks.push(chunk);}});});}async runBuild(compiler,compilation){const outputOptions={...compiler.options.output};if(typeof this.options.filename==='function'){outputOptions.filename=this.options.filename(outputOptions.filename);}else{outputOptions.filename=this.options.filename;}if(typeof this.options.chunkFilename==='function'){outputOptions.chunkFilename=this.options.chunkFilename(outputOptions.chunkFilename);}else{outputOptions.chunkFilename=this.options.chunkFilename;}let plugins=(compiler.options.plugins||[]).filter(c=>!(this.options.excludedPlugins.includes(c.constructor.name)||c.constructor.name===PLUGIN_NAME));// Add the additionalPlugins
plugins=plugins.concat(this.options.additionalPlugins);/**
* We are deliberately not passing plugins in createChildCompiler.
* All webpack does with plugins is to call `apply` method on them
* with the childCompiler.
* But by then we haven't given childCompiler a fileSystem or other options
* which a few plugins might expect while execution the apply method.
* We do call the `apply` method of all plugins by ourselves later in the code
*/const childCompiler=compilation.createChildCompiler(PLUGIN_NAME,outputOptions);childCompiler.context=compiler.context;childCompiler.inputFileSystem=compiler.inputFileSystem;childCompiler.outputFileSystem=compiler.outputFileSystem;// Call the `apply` method of all plugins by ourselves.
if(Array.isArray(plugins)){for(const plugin of plugins){plugin.apply(childCompiler);}}let compilerEntries=compiler.options.entry;if(typeof compilerEntries==='function'){compilerEntries=await compilerEntries();}if(typeof compilerEntries==='string'){compilerEntries={index:compilerEntries};}Object.keys(compilerEntries).forEach(entry=>{const entryFiles=compilerEntries[entry];if(Array.isArray(entryFiles)){new MultiEntryPlugin(compiler.context,entryFiles,entry).apply(childCompiler);}else{new SingleEntryPlugin(compiler.context,entryFiles,entry).apply(childCompiler);}});// Convert entry chunk to entry file
new JsonpTemplatePlugin().apply(childCompiler);const optimization=compiler.options.optimization;if(optimization){if(optimization.splitChunks){new SplitChunksPlugin(Object.assign({},optimization.splitChunks)).apply(childCompiler);}if(optimization.runtimeChunk){new RuntimeChunkPlugin(Object.assign({},optimization.runtimeChunk)).apply(childCompiler);}}// Hold back the main compilation until our Child Compiler has completed so its assets get optimized
const child=new Promise((resolve,reject)=>{// Defer the child compiler until known main thread "dead time" (while Terser is doing minification in the background)
let started=false;compilation.hooks.optimizeChunkAssets.intercept({call:()=>{// only run the first time optimizeChunkAssets is called
if(started)return;started=true;// Delay the Child Compiler until optimizeChunkAssets has had time to send work to the Terser pool
setTimeout(()=>{this.updateOptions(childCompiler);childCompiler.runAsChild((err,_entries,childCompilation)=>{if(err){return reject(err);}if(childCompilation.errors.length>0){return reject(childCompilation.errors[0]);}this.updateAssets(compilation,childCompilation);resolve();});},500);}});});compilation.hooks.optimizeAssets.tapPromise(PLUGIN_NAME,()=>child);}}exports.NextEsmPlugin=NextEsmPlugin;
//# sourceMappingURL=next-esm-plugin.js.map