// @flow import {Transformer} from '@parcel/plugin'; import path from 'path'; import posthtml from 'posthtml'; import {parser as parse} from 'posthtml-parser'; import {render} from 'posthtml-render'; import nullthrows from 'nullthrows'; import semver from 'semver'; import {relativePath} from '@parcel/utils'; import loadPlugins from './loadPlugins'; export default (new Transformer({ async loadConfig({config, options, logger}) { let configFile = await config.getConfig( ['.posthtmlrc', '.posthtmlrc.js', 'posthtml.config.js'], { packageKey: 'posthtml', }, ); if (configFile) { let isJavascript = path.extname(configFile.filePath) === '.js'; if (isJavascript) { // We have to invalidate on startup in case the config is non-deterministic, // e.g. using unknown environment variables, reading from the filesystem, etc. logger.warn({ message: 'WARNING: Using a JavaScript PostHTML config file means losing out on caching features of Parcel. Use a .posthtmlrc (JSON) file whenever possible.', }); config.invalidateOnStartup(); // Also add the config as a dev dependency so we attempt to reload in watch mode. config.addDevDependency({ specifier: relativePath( path.dirname(config.searchPath), configFile.filePath, ), resolveFrom: config.searchPath, }); } // Load plugins. This must be done before adding dev dependencies so we auto install. let plugins = await loadPlugins( configFile.contents.plugins, config.searchPath, options, ); // Now add dev dependencies so we invalidate when they change. let pluginArray = Array.isArray(configFile.contents.plugins) ? configFile.contents.plugins : Object.keys(configFile.contents.plugins); for (let p of pluginArray) { if (typeof p === 'string') { config.addDevDependency({ specifier: p, resolveFrom: configFile.filePath, }); } } configFile.contents.plugins = plugins; // tells posthtml that we have already called parse configFile.contents.skipParse = true; delete configFile.contents.render; return configFile.contents; } }, canReuseAST({ast}) { return ast.type === 'posthtml' && semver.satisfies(ast.version, '^0.4.0'); }, async parse({asset, config}) { // if we don't have a config it is posthtml is not configure, don't parse if (!config) { return; } return { type: 'posthtml', version: '0.4.1', program: parse(await asset.getCode(), { lowerCaseAttributeNames: true, sourceLocations: true, xmlMode: asset.type === 'xhtml', }), }; }, async transform({asset, config}) { if (!config) { return [asset]; } let ast = nullthrows(await asset.getAST()); let res = await posthtml(config.plugins).process(ast.program, { ...config, plugins: config.plugins, }); if (res.messages) { await Promise.all( res.messages.map(({type, file: filePath}) => { if (type === 'dependency') { return asset.invalidateOnFileChange(filePath); } return Promise.resolve(); }), ); } asset.setAST({ type: 'posthtml', version: '0.4.1', program: JSON.parse(JSON.stringify(res.tree)), // posthtml adds functions to the AST that are not serializable }); return [asset]; }, generate({ast, asset}) { return { content: render(ast.program, { closingSingleTag: asset.type === 'xhtml' ? 'slash' : undefined, }), }; }, }): Transformer);