UNPKG

@rall3n/typoscript-webpack-plugin

Version:

A simple plugin to generate typoscript statements to include generated assets (Forked from Michael Marcenich)

415 lines (366 loc) 12.6 kB
'use strict'; const fs = require('fs'); const path = require('path'); const pick = require('just-pick'); const merge = require('deepmerge'); const version = require('webpack').version; /** internals */ const pluginDefaults = { filename: 'WebpackAssets.typoscript', typoScriptPublicPath: '/fileadmin/Resources/Public/', typoScriptRootPath: 'page', typoScriptIncludeTypeDefaults: { js: 'includeJSFooter', css: 'includeCSS' }, chunks: null, loading: { type: 'default', background: '#2c3e50' }, usePosix: true }; const convertIncludeTypes = option => { if (typeof option === 'string') { option = { all: option }; } return option; }; const convertAdditionalTypoScript = option => { if (option) { if (typeof option === 'string') { option = { all: [option] }; } else if (option instanceof Array) { option = { all: option }; } else { ['all', 'js', 'css'].forEach(extension => { if ( option[extension] && typeof option[extension] === 'string' ) { option[extension] = [option[extension]]; } }); } } return option; }; const getChunkOptions = (chunk, options = {}, defaults = {}) => { if (options.includeTypes) { options.includeTypes = convertIncludeTypes(options.includeTypes); } if (options.additionalTypoScript) { options.additionalTypoScript = convertAdditionalTypoScript( options.additionalTypoScript ); } if (options.chunkFiles) { delete options.chunkFiles; } if (defaults.chunkFiles) { delete defaults.chunkFiles; } const chunkFiles = Array.from(chunk.files); defaults = merge(defaults, pick(chunk, ['id', 'name'])); defaults = merge(defaults, { chunkFiles }); return merge(defaults, options); }; class TypoScriptPlugin { constructor(options = {}) { /** @todo BRAKING CHANGE: remove */ pluginDefaults.loading = false; /** @todo BRAKING CHANGE: change to filename */ const pluginOptions = typeof options === 'string' ? { outputPath: options } : options; /** @todo BRAKING CHANGE: set compiler.options.output.path as default output path */ if ( !pluginOptions.outputPath || !path.isAbsolute(pluginOptions.outputPath) ) { pluginOptions.outputPath = path.dirname(module.parent.filename); } if ( typeof pluginOptions.loading === 'boolean' && pluginOptions.loading ) { pluginOptions.loading = { type: 'default', background: '#2c3e50' }; } else if (typeof pluginOptions.loading === 'string') { pluginOptions.loading = { type: pluginOptions.loading, background: '#2c3e50' }; } this.options = merge(pluginDefaults, pluginOptions); if (this.options.typoScriptIncludeTypeDefaults) { this.options.typoScriptIncludeTypeDefaults = convertIncludeTypes( this.options.typoScriptIncludeTypeDefaults ); } if (this.options.typoScriptAdditionalDefaults) { this.options.typoScriptAdditionalDefaults = convertAdditionalTypoScript( this.options.typoScriptAdditionalDefaults ); } } generateTypoScript(chunk, options = {}) { options = getChunkOptions(chunk, options, { includeTypes: this.options.typoScriptIncludeTypeDefaults, additionalTypoScript: this.options.typoScriptAdditionalDefaults }); const output = []; options.chunkFiles.forEach(asset => { const assetOutput = []; const [, extension] = asset.match(/.*?(?:\.([^.]+))?$/); const name = options.customName || 'webpack_' + (options.name || options.id); if (!extension || !['js', 'css'].includes(extension)) { return; } assetOutput.push( name + ' = ' + path[this.options.usePosix ? 'posix' : 'win32'].join( this.options.typoScriptPublicPath, asset ) ); const additionalTypoScript = []; if (options.additionalTypoScript) { if (options.additionalTypoScript.all) { additionalTypoScript.push( ...options.additionalTypoScript.all ); } if (options.additionalTypoScript[extension]) { additionalTypoScript.push( ...options.additionalTypoScript[extension] ); } } if (this.options.loading) { if (extension === 'css') { additionalTypoScript.unshift( 'allWrap = <noscript class="webpack-plugin-defer">|</noscript>' ); } else if (extension === 'js') { additionalTypoScript.unshift('async = 1'); additionalTypoScript.unshift('defer = 1'); } } assetOutput.push( ...additionalTypoScript.map( typoScript => name + '.' + typoScript.replace(/^\./, '') ) ); if (options.includeTypes) { if (options.includeTypes.all) { output.push( options.includeTypes.all + ' {', ...assetOutput, '}' ); } if (options.includeTypes[extension]) { output.push( options.includeTypes[extension] + ' {', ...assetOutput, '}' ); } } }); return output.join('\n'); } generateLoaderTypoScript(options = {}, compilation) { const localPath = path.join(__dirname, 'loading'); const externalPath = []; if ( typeof options.customSource === 'string' && path.isAbsolute(options.customSource) ) { externalPath.push(options.customSource); if (options.type) { externalPath.push(type); } } else if (!options.type) { throw new Error( '[typoscript-webpack-plugin]: Either loading.type or loading.customSource must be defined' ); } const publicPath = this.options.typoScriptPublicPath; const inputSrc = { js: fs.readFileSync(path.join(localPath, 'script.min.js'), 'utf8') }; if (externalPath.length) { inputSrc.css = fs.readFileSync( path.join(...externalPath, 'style.css'), 'utf8' ); } else { inputSrc.css = fs.readFileSync( path.join(localPath, options.type, 'style.css'), 'utf8' ); } inputSrc.css = ` #webpack-plugin-loader { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 10000; background: ${options.background}; -webkit-transition: opacity 0.5s; transition: opacity 0.5s; } ${inputSrc.css} `; if (externalPath.length) { inputSrc.html = fs.readFileSync( path.join(...externalPath, 'template.html'), 'utf8' ); } else { inputSrc.html = fs.readFileSync( path.join(localPath, options.type, 'template.html'), 'utf8' ); } const typoScript = []; const scriptFilename = `webpack-loading.min.js`; const scriptPublicPath = path[ this.options.usePosix ? 'posix' : 'win32' ].join(publicPath, scriptFilename); // emit js in a file this.emitAsset(compilation, scriptFilename, inputSrc.js); // include js in typoscript typoScript.push( 'includeJSFooterlibs {', `webpack_loading = ${scriptPublicPath}`, 'webpack_loading.excludeFromConcatenation = 1', 'webpack_loading.async = 1', 'webpack_loading.defer = 1', '}' ); // include inline style in typoscript typoScript.push( 'headerData {', '11389465 = TEXT', '11389465.value(', '<style type="text/css">', inputSrc.css .replace(/^\s*|\s*([{};])\s*|\s*$/g, '$1') .replace(/(\s)\s+/g, '$1'), '</style>', ')', '}' ); // include html template in typoscript typoScript.push( 'footerData {', '11389465 = TEXT', '11389465.value(', '<div id="webpack-plugin-loader">', inputSrc.html.replace(/^\s*|\s*$/g, '').replace(/(\s)\s+/g, '$1'), '</div>', ')', '}' ); return typoScript; } emitTypoScript(compilation, callback) { const outputLines = []; const compilationChunks = Array.from(compilation.chunks); if (this.options.chunks) { this.options.chunks.forEach(chunkOptions => { if (typeof chunkOptions === 'string') { chunkOptions = { name: chunkOptions }; } const chunk = compilationChunks.find(chunk => chunkOptions.id ? chunkOptions.id === chunk.id : chunkOptions.name === chunk.name ); outputLines.push(this.generateTypoScript(chunk, chunkOptions)); }); } else { outputLines.push( ...compilationChunks.map(chunk => this.generateTypoScript(chunk) ) ); } if (this.options.loading) { outputLines.push( ...this.generateLoaderTypoScript( this.options.loading, compilation ) ); } const output = [ this.options.typoScriptRootPath + ' {', ...outputLines, '}' ].join('\n'); const outputPath = path.join( path.relative( compilation.options.output.path, this.options.outputPath ), this.options.filename ); this.emitAsset(compilation, outputPath, output); callback && callback(); } apply(compiler) { if (!version.startsWith('5')) { compiler.hooks.emit.tapAsync( 'TypoScriptPlugin', (compilation, callback) => this.emitTypoScript(compilation, callback) ); } else { compiler.hooks.thisCompilation.tap( 'TypoScriptPlugin', compilation => compilation.hooks.processAssets.tapAsync( { name: 'TypoScriptPlugin', stage: compiler.webpack.Compilation .PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE }, (compilationAssets, callback) => this.emitTypoScript(compilation, callback) ) ); } } /** * @param {string} filename * @param {string} content */ emitAsset(compilation, filename, content) { if (!version.startsWith('5')) { compilation.assets[filename] = { source: () => content, size: () => content.length }; } else { const { RawSource } = compilation.compiler.webpack.sources; compilation.emitAsset(filename, new RawSource(content), { size: content.length }); } } } module.exports = TypoScriptPlugin;