packages/docpack-examples-compiler/lib/ExamplesCompiler.js (114 lines of code) (raw):
var tools = require('webpack-toolkit');
var ChildCompiler = require('webpack-toolkit/lib/ChildCompiler');
var merge = require('merge-options');
var loader = require('./loader');
var getHash = require('loader-utils').getHashDigest;
var format = require('util').format;
var Docpack = require('docpack');
var DocpackPlugin = require('docpack/lib/utils/DocpackPlugin');
var JsonpTemplatePlugin = require("webpack/lib/JsonpTemplatePlugin");
var WEBPACK_VERSION = tools.getWebpackVersion(true);
/**
* @typedef {ChildCompilerConfig} ExamplesCompilerConfig
*/
/**
* @const
* @type {String}
*/
var SHARED_DATA_LOADER_PATH = require.resolve('./loader');
var defaultConfig = {
/**
* Used for loaders matching, can be overridden via ExampleFile attrs.filename
*/
filename: 'example.[type]',
/**
* Used for naming emitted files.
* Extension will be appended automatically (technically - the name of entry point).
*/
outputFilename: 'examples/[hash]',
/**
* Don't touch this, only for testing purposes!
*/
applyParentCompilerPlugins: true,
/**
* Compiler name
*/
name: 'ExamplesCompiler'
};
/**
* @param {Compilation} compilation
* @param {ExamplesCompilerConfig} [config]
* @extends ChildCompiler
* @constructor
* @class
*/
function ExamplesCompiler(compilation, config) {
if (this instanceof ExamplesCompiler == false) {
return new ExamplesCompiler(compilation, config);
}
this.files = [];
this.config = merge(defaultConfig, config || {});
ChildCompiler.call(this, compilation, this.config);
var compiler = this._compiler;
/**
* We need to create named entries, so child compiler output
* filename should be set to [name] (name of entry)
*/
compiler.options.output.filename = '[name].js';
this._compiler.apply(
new JsonpTemplatePlugin()
);
/**
* We will extract example files content for compilation via special loader,
* so we share array with example files between compiler and loader
*/
loader.plugInCompiler(compiler, this.files);
if (this.config.applyParentCompilerPlugins) {
this.applyParentCompilerPlugins();
}
}
module.exports = ExamplesCompiler;
ExamplesCompiler.defaultConfig = defaultConfig;
/**
* @param {ExampleFile} file
* @param {String} filename ExampleFile virtual filename to match through loaders and find loaders for process it
* @param {Object} loadersConfig Module loaders Webpack config to search in
* @returns {Array<Object>}
*/
ExamplesCompiler.getLoadersToProcessExampleFile = function (file, filename, loadersConfig) {
var filenameToMatch = (file.attrs && typeof file.attrs.filename == 'string')
? file.attrs.filename
: filename.replace('[type]', file.type);
return tools.getMatchedLoaders(loadersConfig, filenameToMatch);
};
ExamplesCompiler.prototype = Object.create(ChildCompiler.prototype);
/**
* @type {ExamplesCompilerConfig}
*/
ExamplesCompiler.prototype.config = null;
/**
* @type {Array<ExampleFile>}
*/
ExamplesCompiler.prototype.files = null;
ExamplesCompiler.prototype.hasHotModuleReplacement = function () {
var parentCompilerOptions = this._compiler.parentCompilation.compiler.options;
return parentCompilerOptions.devServer && parentCompilerOptions.devServer.hot === true;
};
ExamplesCompiler.prototype.applyParentCompilerPlugins = function() {
var compiler = this._compiler;
var parentCompilerPlugins = compiler.parentCompilation.compiler.options.plugins || [];
parentCompilerPlugins
.filter(function(plugin) {
return !(plugin instanceof Docpack) && !(plugin instanceof DocpackPlugin)
})
.forEach(function(plugin) {
compiler.apply(plugin);
});
};
/**
* @param {ExampleFile} file
* @returns {Array<Object>}
*/
ExamplesCompiler.prototype.getLoadersToProcessExampleFile = function(file) {
var filename = this.config.filename;
var compilerModuleConfig = this._compiler.options.module;
return ExamplesCompiler.getLoadersToProcessExampleFile(file, filename, compilerModuleConfig);
};
/**
* @param {ExampleFile} file
* @param {String} resourcePath Path to the origin resource of example file
* @returns {{entryName: String, loaders: Array<Object>, loadersRequest: String, fullRequest: String}}
*/
ExamplesCompiler.prototype.addFile = function(file, resourcePath) {
// TODO
// ExampleFile optionally may have `context` property for proper dependencies resolving
var compilationContext = file.context || this._compiler.context;
// Add file
this.files.push(file);
var fileIndex = this.files.indexOf(file);
// Get loaders to process example file
var matchedLoaders = this.getLoadersToProcessExampleFile(file);
// Shared loader config
var sharedDataLoaderConfig = {};
sharedDataLoaderConfig['loader'] = SHARED_DATA_LOADER_PATH;
sharedDataLoaderConfig[WEBPACK_VERSION === '1' ? 'query' : 'options'] = {
path: fileIndex.toString() + '.content',
hash: getHash(file.content)
};
var loadersRequest = matchedLoaders
.concat([sharedDataLoaderConfig])
.map(tools.stringifyLoaderConfig)
.join('!');
var fullRequest = format('!!%s!%s', loadersRequest, resourcePath);
// Get file output name
var entryName = this.getOutputFilename(file, resourcePath);
if (this.hasHotModuleReplacement()) {
var serverOpts = this._compiler.parentCompilation.compiler.options.devServer;
var domain = `${serverOpts.https ? 'https' : 'http'}://${serverOpts.host}:${serverOpts.port}`;
var clientEntryPath = require.resolve("webpack-dev-server/client/");
var entries = [`${clientEntryPath}?${domain}`];
if (serverOpts.hotOnly) {
entries.push('webpack/hot/only-dev-server');
} else if (serverOpts.hot) {
entries.push('webpack/hot/dev-server');
}
entries.push(fullRequest);
fullRequest = entries;
}
// Add entry to compiler
this.addEntry(fullRequest, entryName, compilationContext);
return {
file: file,
entryName: entryName,
loaders: matchedLoaders,
loadersRequest: loadersRequest,
fullRequest: fullRequest
}
};
/**
* @param {ExampleFile} file
* @param {String} resourcePath
* @returns {String}
*/
ExamplesCompiler.prototype.getOutputFilename = function(file, resourcePath) {
var outputFilename = this.config.outputFilename;
/**
* Fixes case when file.content string is defined, but empty
* @see https://github.com/webpack/loader-utils/blob/master/index.js#L274
*/
var content = file.content === '' ? ' ' : file.content;
var entryName = tools.interpolateName(outputFilename, {
path: resourcePath,
content: content,
context: file.context || this._compiler.context
});
return entryName;
};