diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 0558174..0000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["es2015", "stage-2"] -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e402294 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @michael-ciniawsky @GitScrum diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..1bb867e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,35 @@ +> Briefly describe the issue you are experiencing (or the feature you want to see +added to the plugin). Tell us what you were trying to do and what happened +instead. Remember, this is _not_ a place to ask questions. For that, go to +http://gitter.im/posthtml/posthtml + +### `📝 Details` + +> Describe in more detail the problem you have been experiencing, if necessary. + +### `❌ Error (Logs|Stacks)` + +> Create a [gist](https://gist.github.com) which is a paste of your **full** +logs, and link them here. + +> âš ī¸ Do **not** paste your full logs here (or at least hide them by using a `
` block), as it will make this issue long and hard +to read! If you are reporting a bug, **always** include logs! + +### `â™ģī¸ Reproduction (Code)` + +> :warning: Please remember that, with sample code; it's easier to reproduce a bug and much +faster to fix it. + +> 🔗 Please refer to a simple code example. + +```bash +$ git clone https://github.com// +``` + +### `🌐 Environment` + +> â„šī¸ Please provide information about your current environment. + +|OS|node|npm/yarn|package| +|:-:|:--:|:-:|:------:| +|[name][version]|[version]|[version]|[version]| diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b8f0333 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,59 @@ +### `Notable Changes` + +> âœī¸ Describe the big picture of your changes here to communicate to the maintainers +why we should accept this pull request. If it fixes a bug or resolves a feature +request, be sure to link to that issue down below. + +#### `Commit Message Summary (CHANGELOG)` + +``` +commit message body... +``` + +### `Type` + +> â„šī¸ What types of changes does your code introduce? + +> 👉 _Put an `x` in the boxes that apply and delete all others_ + +- [ ] CI +- [ ] Fix +- [ ] Perf +- [ ] Docs +- [ ] Test +- [ ] Chore +- [ ] Style +- [ ] Build +- [ ] Feature +- [ ] Refactor + +### `SemVer` + +> â„šī¸ What changes to the current `semver` range does your PR introduce? + +> 👉 _Put an `x` in the boxes that apply and delete all others_ + +- [ ] Bug (:label: Patch) +- [ ] Feature (:label: Minor) +- [ ] Breaking Change (:label: Major) + +### `Issues` + +> â„šī¸ What issue (if any) are closed by your PR? + +> 👉 _Replace `#1` with the issue number that applies and remove the ``` ` ```_ + +- Fixes `#1` + +### `Checklist` + +> â„šī¸ You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. This is a reminder of what we are going to look for before merging your code. + +> 👉 _Put an `x` in the boxes that apply and delete all others._ + +- [ ] I have [read and sign the CLA](https://cla.js.foundation/webpack/webpack.js.org) +- [ ] I checked out the [development guide](https://webpack.js.org/development/) for API and development guidelines +- [ ] Lint and unit tests pass with my changes +- [ ] I have added tests that prove my fix is effective/works +- [ ] I have added necessary documentation (if appropriate) +- [ ] Any dependent changes are merged and published in downstream modules diff --git a/.gitignore b/.gitignore index c5b30b3..d6143bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,14 @@ +# OS + .DS_Store -.nyc_output +._* + +# NODEJS + +npm-debug.log + +dmd +jest coverage +jsdoc-api node_modules diff --git a/.npmignore b/.npmignore index fc7f2c5..f831b34 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,18 @@ +# FILES + .travis.yml .gitignore .editorconfig -.babelrc -CONTRIBUTING.md + +npm-debug.log + +# DIRECTORIES + +.github + +dmd test -src +jest +coverage +jsdoc-api +node_modules diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml index d22116a..d84955b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,13 @@ - -sudo: false language: node_js + node_js: + - stable + - lts/* - 6 -after_script: - - npm run coveralls + - 4 + +after_success: +- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" + +notifications: + email: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..13bdc0b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Change Log + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + + +# [1.0.0](https://github.com/posthtml/posthtml-loader/compare/v0.10.3...v1.0.0) (2017-12-16) + + +### Bug Fixes + +* **Error:** add missing `'use strict'` pragma ([1316255](https://github.com/posthtml/posthtml-loader/commit/1316255)) + + +### Features + +* add parser query option ([6650acc](https://github.com/posthtml/posthtml-loader/commit/6650acc)) +* **index:** add `options` validation (`schema-utils`) ([7bd5896](https://github.com/posthtml/posthtml-loader/commit/7bd5896)) +* **index:** support `posthtml.config.js` && `result.messages` ([e05b44c](https://github.com/posthtml/posthtml-loader/commit/e05b44c)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db1216c..9a50035 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,56 +1,60 @@ -# Contributing Guidelines - -Contributions welcome! - -**Before spending lots of time on something, ask for feedback on your idea first!** - -Please search issues and pull requests before adding something new to avoid duplicating efforts and conversations. - -This project welcomes non-code contributions, too! The following types of contributions are welcome: - -- **Ideas**: participate in an issue thread or start your own to have your voice heard. -- **Writing**: contribute your expertise in an area by helping expand the included docs. -- **Copy editing**: fix typos, clarify language, and improve the quality of the docs. -- **Formatting**: help keep docs easy to read with consistent formatting. - -## Code Style - -[![standard][standard-image]][standard-url] - -This repository uses [`standard`][standard-url] to maintain code style and consistency, and to avoid style arguments. `npm test` runs `standard` automatically, so you don't have to! - -## Project Governance - -Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. - -### Rules - -There are a few basic ground-rules for contributors: - -1. **No `--force` pushes** or modifying the Git history in any way. -2. **Non-master branches** should be used for ongoing work. -3. **Significant modifications** like API changes should be subject to a **pull request** to solicit feedback from other contributors. -4. **Pull requests** are *encouraged* for all contributions to solicit feedback, but left to the discretion of the contributor. - -### Releases - -Declaring formal releases remains the prerogative of the project maintainer. - -### Changes to this arrangement - -This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. - -## Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -- (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or - -- (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or - -- (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. - -- (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. - -[standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg -[standard-url]: https://github.com/feross/standard +You want to help? You rock! Now, take a moment to be sure your contributions make sense to everyone else. + +## Reporting Issues + +Found a problem? Want a new feature? + +- See if your issue or idea has [already been reported]. +- Provide a [reduced test case] or a [live example]. + +Remember, a bug is a _demonstrable problem_ caused by _our_ code. + +## Submitting Pull Requests + +Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. + +1. To begin, [fork this project], clone your fork, and add our upstream. + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com//PLUGIN_NAME + # Navigate to the newly cloned directory + cd PLUGIN_NAME + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com/GITHUB_NAME/PLUGIN_NAME + # Install the tools necessary for development + npm install + ``` + +2. Create a branch for your feature or fix: + ```bash + # Move into a new branch for a feature + git checkout -b feature/thing + ``` + ```bash + # Move into a new branch for a fix + git checkout -b fix/something + ``` + +3. Be sure your code follows our practices. + ```bash + # Test current code + npm run test + ``` + +4. Push your branch up to your fork: + ```bash + # Push a feature branch + git push origin feature/thing + ``` + ```bash + # Push a fix branch + git push origin fix/something + ``` + +5. Now [open a pull request] with a clear title and description. + +[already been reported]: issues +[fork this project]: fork +[live example]: http://codepen.io/pen +[open a pull request]: https://help.github.com/articles/using-pull-requests/ +[reduced test case]: https://css-tricks.com/reduced-test-cases/ diff --git a/LICENSE b/LICENSE index 852ee70..78d765b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -The MIT License (MIT) +License (MIT) -Copyright (c) 2016 Michael Ciniawsky, Jeff Escalante +Copyright (c) PostHTML Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LOADER.md b/LOADER.md new file mode 100644 index 0000000..0507ea3 --- /dev/null +++ b/LOADER.md @@ -0,0 +1,16 @@ + + +## posthtml-loader(html) ⇒ String +PostHTML Loader + +**Kind**: global function +**Returns**: String - html HTML +**Requires**: module:loader-utils, module:schema-utils, module:posthtml, module:posthtml-load-config +**Version**: 1.0.0 +**Author**: Michael Ciniawsky (@michael-ciniawsky) +**License**: MIT + +| Param | Type | Description | +| --- | --- | --- | +| html | String | HTML | + diff --git a/README.md b/README.md index 9657289..2abc27e 100644 --- a/README.md +++ b/README.md @@ -1,112 +1,244 @@ -# PostHTML Loader - [![npm][npm]][npm-url] -[![tests][travis]][travis-url] -[![dependencies][deps]][deps-url] +[![node][node]][node-url] +[![deps][deps]][deps-url] +[![tests][tests]][tests-url] [![coverage][cover]][cover-url] -[![Code Style][style]][style-url] +[![code style][style]][style-url] +[![chat][chat]][chat-url] -A PostHTML loader for webpack +
+ + + + +

PostHTML Loader

+
-## Installation +

Install

-```sh -npm i html-loader posthtml-loader --save +```bash +npm i -D posthtml-loader ``` -## Usage - -The posthtml loader must be used with at least one other loader in order to integrate with webpack correctly. For most use cases, the [html-loader](https://github.com/webpack/html-loader) is recommended. If you want to export the html string directly for use in javascript or webpack plugins, we recommend the [source-loader](https://github.com/static-dev/source-loader). Whichever loader you choose, it should be the first loader, followed by posthtml, as you will see in the examples below. - -Options can be passed through a `posthtml` option directly on the webpack config object. It accepts an array, an object, or a function that returns an array or object. If it's an array, it should contain plugins. If it's an object, it can contain a `plugins` key, which is an array of plugins and an optional `parser` key which allows you to pass in a custom parser. Any other key will apply to the `pack` querystring parameter, documented below. +

Usage

-Basic configuration example: +```js +import html from './file.html' +``` +**webpack.config.js** ```js -// webpack.config.js module: { - loaders: [{ - test: /\.html$/, - loader: 'html!posthtml' - }] + rules: [ + { + test: /\.html$/, + use: [ + 'html-loader', + { + loader: 'posthtml-loader', + options: { + ident: 'posthtml', + parser: 'PostHTML Parser' + plugins: [ + /* PostHTML Plugins */ + require('posthtml-plugin')(options) + ] + } + } + ] + } + ] }, -posthtml: [/* plugins here */] ``` -### Plugin Packs +

Options

+ +|Name|Type|Default|Description| +|:--:|:--:|:-----:|:----------| +|**[`config`](#config)**|`{Object}`|`undefined`|PostHTML Config| +|**[`parser`](#parser)**|`{String/Function}`|`undefined`|PostHTML Parser| +|**[`plugins`](#plugins)**|`{Array/Function}`|`[]`|PostHTML Plugins| -If you need to apply different sets of plugins to different groups of files, you can use a **plugin pack**. Just add `pack=[name]` as a querystring option, and return an object from the `posthtml` config option with a key matching the pack name, and the value being an array of plugins. +### `Config` + +|Name|Type|Default|Description| +|:--:|:--:|:-----:|:----------| +|**[`path`](#path)**|`{String}`|`loader.resourcePath`|PostHTML Config Path| +|**[`ctx`](#context)**|`{Object}`|`{}`|PostHTML Config Context| + +If you want to use are shareable config file instead of inline options in your `webpack.config.js` create a `posthtml.config.js` file and placed it somewhere down the file tree in your project. The nearest config relative to `dirname(file)` currently processed by the loader applies. This enables **Config Cascading**. Despite some edge cases the config file will be loaded automatically and **no** additional setup is required. If you don't intend to use Config Cascading, it's recommended to place `posthtml.config.js` in the **root** `./` of your project + +``` +|– src +||– components +|||– component.html +|||– posthtml.config.js (components) +||– index.html +| +|– posthtml.config.js (index) +|– webpack.config.js +``` +#### `Path` + +If you normally place all your config files in a separate folder e.g `./config` it is necessary to explicitly set the config path in `webpack.config.js` + +**webpack.config.js** ```js -// webpack.config.js -module: { - loaders: [{ - test: /\\.special\.html$/, - loader: 'html!posthtml?pack=special' - }] -}, -posthtml: { - plugins: [/* plugins that apply anything that's not using a pack */], - special: [ /* plugins specific to the "special" pack */ ], +{ + loader: 'posthtml-loader', + options: { + config: { + path: 'path/to/.config/' + } + } } ``` -### Using a Function +#### `Context` -You can also return a function from the `posthtml` config value, if you need to for any reason. The function passes along the [loader context](https://webpack.github.io/docs/loaders.html#loader-context) as an argument, so you can get information about the file currently being processed from this and pass it to plugins if needed. For example: +|Name|Type|Default|Description| +|:--:|:--:|:-----:|:----------| +|`env`|`{String}`|`'development'`|process.env.NODE_ENV| +|`file`|`{Object}`|`{ dirname, basename, extname }`|File| +|`options`|`{Object}`|`{}`|Plugin Options (Context)| +[**posthtml.config.js**](https://github.com/posthtml/posthtml-load-config) ```js -// webpack.config.js -module: { - loaders: [{ - test: /\.html$/, - loader: 'html!posthtml' - }] -}, -posthtml: (ctx) => { - return [examplePlugin({ filename: ctx.resourcePath })] +module.exports = ({ file, options, env }) => ({ + parser: 'posthtml-sugarml' + plugins: { + 'posthtml-include': options.include + 'posthtml-content': options.content + 'htmlnano': env === 'production' ? {} : false + } +}) +``` + +**webpack.config.js** +```js +{ + loader: 'posthtml-loader', + options: { + config: { + ctx: { + include: {...options} + content: {...options} + } + } + } +} +``` + +### `Parser` + +If you want to use a custom parser e.g [SugarML](https://github.com/posthtml/sugarml), you can pass it in under the `parser` key in the loader options + +#### `{String}` + +**webpack.config.js** +```js +{ + loader: 'posthtml-loader', + options: { + parser: 'posthtml-sugarml' + } +} +``` + +#### `{Function}` + +**webpack.config.js** +```js +{ + loader: 'posthtml-loader', + options: { + parser: require('posthtml-sugarml')() + } } ``` -### Custom Parser +### `Plugins` -If you want to use a custom parser, you can pass it in under the `parser` key. Below is an example with the [sugarml parser](https://github.com/posthtml/sugarml): +Plugins are specified under the `plugins` key in the loader options +#### `{Array}` + +**webpack.config.js** ```js -// webpack.config.js -const sugarml = require('sugarml') +{ + loader: 'posthtml-loader', + options: { + plugins: [ + require('posthtml-plugin')() + ] + } +} +``` -module: { - loaders: [{ - test: /\\.special\.html$/, - loader: 'html!posthtml?pack=special' - }] -}, -posthtml: { - plugins: [/* posthtml plugins */], - parser: sugarml +#### `{Function}` + +**webpack.config.js** +```js +{ + loader: 'posthtml-loader', + options: { + plugins (loader) { + return [ + require('posthtml-plugin')() + ] + } + } } ``` -## License & Contributing +

Maintainer

+ + + + + + + +
+ +
+ Michael Ciniawsky +
+ +

Contributors

+ + + + + + + +
+ +
+ Ivan Demidov +
-- Licensed under [MIT](LICENSE) -- See [contributing guidelines](CONTRIBUTING.md) [npm]: https://img.shields.io/npm/v/posthtml-loader.svg [npm-url]: https://npmjs.com/package/posthtml-loader +[node]: https://img.shields.io/node/v/posthtml-loader.svg +[node-url]: https://nodejs.org/ + [deps]: https://david-dm.org/posthtml/posthtml-loader.svg [deps-url]: https://david-dm.org/posthtml/posthtml-loader -[devdeps]: https://david-dm.org/posthtml/posthtml-loader/dev-status.svg -[devdeps-url]: https://david-dm.org/posthtml/posthtml-loader#info=devDependencies +[tests]: http://img.shields.io/travis/posthtml/posthtml-loader.svg +[tests-url]: https://travis-ci.org/posthtml/posthtml-loader + +[cover]: https://coveralls.io/repos/github/posthtml/posthtml-loader/badge.svg +[cover-url]: https://coveralls.io/github/posthtml/posthtml-loader [style]: https://img.shields.io/badge/code%20style-standard-yellow.svg [style-url]: http://standardjs.com/ -[travis]: http://img.shields.io/travis/posthtml/posthtml-loader.svg -[travis-url]: https://travis-ci.org/posthtml/posthtml-loader - -[cover]: https://coveralls.io/repos/github/posthtml/posthtml-loader/badge.svg?branch=master -[cover-url]: https://coveralls.io/github/posthtml/posthtml-loader?branch=master +[chat]: https://badges.gitter.im/posthtml/posthtml.svg +[chat-url]: https://gitter.im/posthtml/posthtml diff --git a/lib/Error.js b/lib/Error.js new file mode 100644 index 0000000..0545947 --- /dev/null +++ b/lib/Error.js @@ -0,0 +1,17 @@ +'use strict' + +class LoaderError extends Error { + constructor(err) { + super(err); + + this.name = 'PostHTML Loader'; + this.message = `\n\n${err.message}\n`; + + // TODO(michael-ciniawsky) + // add 'SyntaxError', 'PluginError', 'PluginWarning' + + Error.captureStackTrace(this, this.constructor); + } +} + +module.exports = LoaderError; diff --git a/lib/index.js b/lib/index.js index 02f422d..1c1661c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,53 +1,163 @@ +'use strict' + +const path = require('path') + const loaderUtils = require('loader-utils') +const validateOptions = require('schema-utils') + +const schema = require('./options.json') + const posthtml = require('posthtml') +const posthtmlrc = require('posthtml-load-config') -module.exports = function (source) { - if (this.cacheable) this.cacheable() +const parseOptions = require('./options') - // configure options - const qs = loaderUtils.parseQuery(this.query) +const LoaderError = require('./Error') +/** + * PostHTML Loader + * + * @author Michael Ciniawsky (@michael-ciniawsky) + * @license MIT + * + * @version 1.0.0 + * + * @requires loader-utils + * @requires schema-utils + * + * @requires posthtml + * @requires posthtml-load-config + * + * @method posthtml-loader + * + * @param {String} html HTML + * + * @return {String} html HTML + */ +module.exports = function loader (html, map, meta) { + // Loader Options + const options = loaderUtils.getOptions(this) || {} + + validateOptions(schema, options, 'PostHTML Loader') + + // Make the loader async const cb = this.async() - let config - try { - config = parseOptions.call(this, this.options.posthtml, qs) - } catch (err) { - return cb(err) - } - - // configure custom parser argument if necessary - const processArgs = [source.toString()] - if (config.parser) { processArgs.push({ parser: config.parser }) } - - // run posthtml - const ph = posthtml(config.plugins) - ph.process.apply(ph, processArgs) - .then((result) => cb(null, result.html), cb) -} + const file = this.resourcePath -function parseOptions (config = [], qs = {}) { - const res = {} + Promise.resolve().then(() => { + const length = Object.keys(options) + .filter((option) => { + switch (option) { + case 'ident': + case 'config': + return + default: + return option + } + }) + .length - // if we have a function, run it - if (typeof config === 'function') { config = config.call(this, this) } + if (length) { + return parseOptions.call(this, options) + } - // if it's not an object at this point, error - if (typeof config !== 'object') { - throw new Error('Configuration must return an array or object') - } + const rc = { + path: path.dirname(file), + ctx: { + file: { + extname: path.extname(file), + dirname: path.dirname(file), + basename: path.basename(file) + }, + options: {} + } + } - // if we now have an array, that represents the plugins directly - if (Array.isArray(config)) { - res.plugins = config - // if not, it's an object. if a plugin pack is being used, use it. - // otherwise, use default plugins - } else { - res.plugins = qs.pack ? config[qs.pack] : config.plugins - } + if (options.config) { + if (options.config.path) { + rc.path = path.resolve(options.config.path) + } - // load in the custom parser if there is one - if (config.parser) { res.parser = config.parser } + if (options.config.ctx) { + rc.ctx.options = options.config.ctx + } + } - return res -} + return posthtmlrc(rc.ctx, rc.path, { argv: false }) + }) + .then((config) => { + if (!config) config = {} + + if (config.file) this.addDependency(config.file) + + if (config.options) { + // Disable overriding `options.to` (`posthtml.config.js`) + if (config.options.to) delete config.options.to + // Disable overriding `options.from` (`posthtml.config.js`) + if (config.options.from) delete config.options.from + } + + let plugins = config.plugins || [] + let options = Object.assign( + { from: file, to: file }, + config.options + ) + + if (typeof options.parser === 'string') { + options.parser = require(options.parser)() + } + + // TODO(michael-ciniawsky) enable if when custom renderer available + // if (typeof options.render === 'string') { + // options.render = require(options.render)() + // } -module.exports.parseOptions = parseOptions + return posthtml(plugins) + .process(html, options) + .then((result) => { + if (result.messages) { + result.messages.forEach((msg) => { + switch (msg.type) { + case 'error': + this.emitError(msg.message) + + break + case 'warning': + this.emitWarning(msg.message) + + break + case 'dependency': + this.addDependency(msg.file) + + break + default: + break + } + }) + } + + html = result.html + + if (this.loaderIndex === 0) { + html = `export default \`${html}\`` + + cb(null, html) + + return null + } + + if (!meta) meta = {} + + meta.ast = { type: 'posthtml', root: result.tree } + meta.messages = result.messages + + cb(null, html, map, meta) + + return null + }) + }) + .catch((err) => { + cb(new LoaderError(err)) + + return null + }) +} diff --git a/lib/options.js b/lib/options.js new file mode 100644 index 0000000..e7f7951 --- /dev/null +++ b/lib/options.js @@ -0,0 +1,22 @@ +'use strict' + +module.exports = function parseOptions (params) { + if (typeof params.plugins === 'function') { + params.plugins = params.plugins.call(this, this) + } + + let plugins + + if (typeof params.plugins === 'undefined') plugins = [] + else if (Array.isArray(params.plugins)) plugins = params.plugins + else plugins = [ params.plugins ] + + const options = {} + + if (typeof params !== 'undefined') { + options.parser = params.parser + // options.render = params.render + } + + return Promise.resolve({ options: options, plugins: plugins }) +} diff --git a/lib/options.json b/lib/options.json new file mode 100644 index 0000000..314f6dd --- /dev/null +++ b/lib/options.json @@ -0,0 +1,35 @@ +{ + "type": "object", + "properties": { + "ident": { + "type": "string" + }, + "config": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "ctx": { + "type": "object" + } + }, + "additionalProperties": false + }, + "parser": { + "oneOf": [ + { "type": "string" }, + { "type": "object" }, + { "instanceof": "Function" } + ] + }, + "plugins": { + "oneOf": [ + { "type": "array" }, + { "type": "object" }, + { "instanceof": "Function" } + ] + } + }, + "additionalProperties": false +} diff --git a/package.json b/package.json index 0ecc277..d7a6dc9 100644 --- a/package.json +++ b/package.json @@ -1,53 +1,55 @@ { "name": "posthtml-loader", - "description": "PostHTML loader for Webpack", - "version": "0.10.3", - "author": "Jeff Escalante", - "ava": { - "verbose": "true", - "serial": "true" + "description": "PostHTML for Webpack", + "version": "1.0.0", + "main": "index.js", + "engines": { + "node": ">= 4" }, - "bugs": { - "url": "https://github.com/posthtml/posthtml-loader/issues" + "files": [ + "lib" + ], + "scripts": { + "lint": "standard --env jest", + "clean": "rm -rf jest coverage jsdoc-api dmd test/builds", + "pretest": "npm run clean", + "test": "jest --verbose --coverage", + "docs": "jsdoc2md lib/index.js > LOADER.md", + "release": "standard-version" }, "dependencies": { - "loader-utils": "^0.2.12", - "posthtml": "^0.9.0" + "loader-utils": "^1.1.0", + "posthtml": "^0.11.0", + "posthtml-load-config": "^1.0.0", + "schema-utils": "^0.4.3" }, "devDependencies": { - "ava": "^0.15.2", - "babel-cli": "^6.10.1", - "babel-preset-es2015": "^6.9.0", - "babel-preset-stage-2": "^6.11.0", - "coveralls": "^2.11.9", - "nyc": "^7.0.0", - "posthtml-custom-elements": "^1.0.3", - "posthtml-exp": "^0.9.0", - "source-loader": "^0.2.0", - "standard": "^7.1.2", - "sugarml": "0.0.1", - "webpack": "^1.13.1", - "when": "^3.7.7" + "coveralls": "^2.0.0", + "del": "^3.0.0", + "jest": "^21.0.0", + "jsdoc-to-markdown": "^3.0.0", + "memory-fs": "^0.4.0", + "posthtml-sugarml": "1.0.0-alpha3", + "standard": "^10.0.0", + "standard-version": "^4.0.0", + "webpack": "^3.0.0" }, - "engine": ">=4", - "homepage": "https://github.com/posthtml/posthtml-loader", "keywords": [ "HTML", "Loader", "PostHTML", "Webpack" ], - "license": "MIT", - "main": "lib", - "repository": { - "type": "git", - "url": "https://github.com/posthtml/posthtml-loader.git" - }, - "scripts": { - "coveralls": "nyc report --reporter=text-lcov | coveralls", - "pretest": "standard", - "test": "npm run build && ava && npm run unbuild", - "build": "mv lib src && babel src -d lib", - "unbuild": "rm -rf lib && mv src lib" - } + "author": "Michael Ciniawsky (@michael-ciniawsky)", + "contributors": [ + { + "name": "Ivan Demidov", + "email": "Scrum@list.ru", + "url": "https://twitter.com/Scrum_" + } + ], + "repository": "https://github.com/posthtml/posthtml-loader.git", + "bugs": "https://github.com/posthtml/posthtml-loader/issues", + "homepage": "https://github.com/posthtml/posthtml-loader", + "license": "MIT" } diff --git a/test/Errors.test.js b/test/Errors.test.js new file mode 100644 index 0000000..bfac0a2 --- /dev/null +++ b/test/Errors.test.js @@ -0,0 +1,12 @@ +'use strict' + +describe('Errors', () => { + test('Validation Error', () => { + const loader = require('../lib') + + const error = () => loader.call({ query: { plugins: 1 } }) + + expect(error).toThrow() + expect(error).toThrowErrorMatchingSnapshot() + }) +}) diff --git a/test/__snapshots__/Errors.test.js.snap b/test/__snapshots__/Errors.test.js.snap new file mode 100644 index 0000000..b7b017e --- /dev/null +++ b/test/__snapshots__/Errors.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Errors Validation Error 1`] = ` +"PostHTML Loader Invalid Options + +options.plugins should be array +options.plugins should be object +options.plugins should pass \\"instanceof\\" keyword validation +options.plugins should match exactly one schema in oneOf +" +`; diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap new file mode 100644 index 0000000..1421543 --- /dev/null +++ b/test/__snapshots__/loader.test.js.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Loader Defaults 1`] = ` +"export default \`
Hello
+\`" +`; diff --git a/test/fixtures/basic/app.js b/test/fixtures/basic/app.js deleted file mode 100644 index 01a7563..0000000 --- a/test/fixtures/basic/app.js +++ /dev/null @@ -1 +0,0 @@ -require('./index.html') diff --git a/test/fixtures/basic/index.html b/test/fixtures/basic/index.html deleted file mode 100644 index d9d3fe7..0000000 --- a/test/fixtures/basic/index.html +++ /dev/null @@ -1 +0,0 @@ -hello world diff --git a/test/fixtures/custom_parser/app.js b/test/fixtures/custom_parser/app.js deleted file mode 100644 index 01a7563..0000000 --- a/test/fixtures/custom_parser/app.js +++ /dev/null @@ -1 +0,0 @@ -require('./index.html') diff --git a/test/fixtures/custom_parser/index.html b/test/fixtures/custom_parser/index.html deleted file mode 100644 index 8d79b31..0000000 --- a/test/fixtures/custom_parser/index.html +++ /dev/null @@ -1 +0,0 @@ -custom hello world diff --git a/test/fixtures/fixture.html b/test/fixtures/fixture.html new file mode 100644 index 0000000..0fd9beb --- /dev/null +++ b/test/fixtures/fixture.html @@ -0,0 +1 @@ +
Hello
diff --git a/test/fixtures/fixture.js b/test/fixtures/fixture.js new file mode 100644 index 0000000..d61d7d7 --- /dev/null +++ b/test/fixtures/fixture.js @@ -0,0 +1 @@ +import html from './fixture.html' diff --git a/test/fixtures/locals/app.js b/test/fixtures/locals/app.js deleted file mode 100644 index 01a7563..0000000 --- a/test/fixtures/locals/app.js +++ /dev/null @@ -1 +0,0 @@ -require('./index.html') diff --git a/test/fixtures/locals/index.html b/test/fixtures/locals/index.html deleted file mode 100644 index 13db462..0000000 --- a/test/fixtures/locals/index.html +++ /dev/null @@ -1 +0,0 @@ -

{{ foo }}

diff --git a/test/fixtures/options/config/posthtml.config.js b/test/fixtures/options/config/posthtml.config.js new file mode 100644 index 0000000..52eec8c --- /dev/null +++ b/test/fixtures/options/config/posthtml.config.js @@ -0,0 +1,7 @@ +module.exports = (ctx) => ({ + to: 'delete.html', + from: 'delete.html', + plugins: [ + ctx.options.plugin ? require('../../plugin')() : false + ] +}) diff --git a/test/fixtures/options/parser/fixture.js b/test/fixtures/options/parser/fixture.js new file mode 100644 index 0000000..fc67d60 --- /dev/null +++ b/test/fixtures/options/parser/fixture.js @@ -0,0 +1 @@ +import html from './fixture.ssml' diff --git a/test/fixtures/options/parser/fixture.ssml b/test/fixtures/options/parser/fixture.ssml new file mode 100644 index 0000000..e115573 --- /dev/null +++ b/test/fixtures/options/parser/fixture.ssml @@ -0,0 +1 @@ +div Hello diff --git a/test/fixtures/plugin.js b/test/fixtures/plugin.js new file mode 100644 index 0000000..6896202 --- /dev/null +++ b/test/fixtures/plugin.js @@ -0,0 +1,15 @@ +'use strict' + +module.exports = function plugin (options) { + options = Object.assign({}, options) + + return function (tree) { + tree.walk((node) => { + if (node.tag === 'div') node.tag = 'section' + + return node + }) + + return tree + } +} diff --git a/test/fixtures/posthtml.config.js b/test/fixtures/posthtml.config.js new file mode 100644 index 0000000..e2b0f4b --- /dev/null +++ b/test/fixtures/posthtml.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('./plugin')() + ] +} diff --git a/test/helpers/compiler.js b/test/helpers/compiler.js new file mode 100644 index 0000000..9add1dd --- /dev/null +++ b/test/helpers/compiler.js @@ -0,0 +1,67 @@ +'use strict' + +const path = require('path') +const del = require('del') +const webpack = require('webpack') +const MemoryFS = require('memory-fs') + +const modules = (config) => { + return { + rules: config.rules + ? config.rules + : config.loader + ? [ + { + test: config.loader.test || /\.txt$/, + use: { + loader: path.resolve(__dirname, '../../lib'), + options: config.loader.options || {} + } + } + ] + : [] + } +} + +const plugins = config => ([ + new webpack.optimize.CommonsChunkPlugin({ + names: ['runtime'], + minChunks: Infinity + }) +].concat(config.plugins || [])) + +const output = (config) => { + return { + path: path.resolve( + __dirname, + `../outputs/${config.output ? config.output : ''}` + ), + filename: '[name].js', + chunkFilename: '[name].chunk.js' + } +} + +module.exports = function (fixture, config, options) { + config = { + devtool: config.devtool || 'sourcemap', + context: path.resolve(__dirname, '..', 'fixtures'), + entry: `./${fixture}`, + output: output(config), + module: modules(config), + plugins: plugins(config) + } + + options = Object.assign({ output: false }, options) + + if (options.output) del.sync(config.output.path) + + const compiler = webpack(config) + + if (!options.output) compiler.outputFileSystem = new MemoryFS() + + return new Promise((resolve, reject) => compiler.run((err, stats) => { + if (err) reject(err) + + resolve(stats) + })) +} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 4ec629e..0000000 --- a/test/index.js +++ /dev/null @@ -1,89 +0,0 @@ -const test = require('ava') -const webpack = require('webpack') -const path = require('path') -const fs = require('fs') -const node = require('when/node') -const customElements = require('posthtml-custom-elements') -const exp = require('posthtml-exp') -const sugarml = require('sugarml') -const fixtures = path.join(__dirname, 'fixtures') - -test('basic', (t) => { - return webpackCompile('basic', [customElements()]) - .then(({outputPath, src}) => { - t.truthy(src.match(/
hello world<\/div>/)) - fs.unlinkSync(outputPath) - }) -}) - -test('config function', (t) => { - return webpackCompile('basic', () => [customElements()]) - .then(({outputPath, src}) => { - t.truthy(src.match(/
hello world<\/div>/)) - fs.unlinkSync(outputPath) - }) -}) - -test('config object', (t) => { - return webpackCompile('basic', { plugins: [customElements()] }) - .then(({outputPath, src}) => { - t.truthy(src.match(/
hello world<\/div>/)) - fs.unlinkSync(outputPath) - }) -}) - -test('plugin packs', (t) => { - return webpackCompile('basic', { special: [customElements()] }, '?pack=special') - .then(({outputPath, src}) => { - t.truthy(src.match(/
hello world<\/div>/)) - fs.unlinkSync(outputPath) - }) -}) - -test('custom parser', (t) => { - return webpackCompile('custom_parser', { - plugins: [customElements()], - parser: sugarml - }).then(({outputPath, src}) => { - t.truthy(src.match(/
hello world<\/div>/)) - fs.unlinkSync(outputPath) - }) -}) - -test('invalid config', (t) => { - return webpackCompile('custom_parser', 5) - .then(() => t.fail('invalid config, no error')) - .catch(({outputPath, err}) => { - t.truthy(err.toString().match(/Error: Configuration must return an array or object/)) - fs.unlinkSync(outputPath) - }) -}) - -test('function called with correct context', (t) => { - return webpackCompile('locals', (ctx) => { - return [exp({ locals: { foo: ctx.resourcePath } })] - }).then(({outputPath, src}) => { - t.truthy(src.match(/test\/fixtures\/locals\/index\.html/)) - fs.unlinkSync(outputPath) - }) -}) - -// Utility: compile a fixture with webpack, return results -function webpackCompile (name, config, qs = '') { - const testPath = path.join(fixtures, name) - const outputPath = path.join(testPath, 'bundle.js') - - return node.call(webpack, { - entry: { output: [path.join(testPath, 'app.js')] }, - output: { path: testPath }, - resolveLoader: { root: path.resolve('../lib') }, - module: { - loaders: [{ test: /\.html$/, loader: `source!index${qs}` }] - }, - posthtml: config - }).then((stats) => { - if (stats.compilation.errors.length) throw stats.compilation.errors - const src = fs.readFileSync(outputPath, 'utf8') - return {outputPath, src} - }).catch((err) => { throw {outputPath, err} }) // eslint-disable-line -} diff --git a/test/loader.test.js b/test/loader.test.js new file mode 100644 index 0000000..ff14a9f --- /dev/null +++ b/test/loader.test.js @@ -0,0 +1,22 @@ +'use strict' + +const webpack = require('./helpers/compiler') + +describe('Loader', () => { + test('Defaults', () => { + const config = { + loader: { + test: /\.html$/, + options: {} + } + } + + return webpack('fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) +}) diff --git a/test/options/__snapshots__/config.test.js.snap b/test/options/__snapshots__/config.test.js.snap new file mode 100644 index 0000000..b721f8a --- /dev/null +++ b/test/options/__snapshots__/config.test.js.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Options config ctx - {Object} 1`] = ` +"export default \`
Hello
+\`" +`; + +exports[`Options config path - {String} 1`] = ` +"export default \`
Hello
+\`" +`; diff --git a/test/options/__snapshots__/parser.test.js.snap b/test/options/__snapshots__/parser.test.js.snap new file mode 100644 index 0000000..b106d4d --- /dev/null +++ b/test/options/__snapshots__/parser.test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Options parser {Object} 1`] = `"export default \`
Hello
\`"`; + +exports[`Options parser {String} 1`] = `"export default \`
Hello
\`"`; diff --git a/test/options/__snapshots__/plugins.test.js.snap b/test/options/__snapshots__/plugins.test.js.snap new file mode 100644 index 0000000..3ad6b08 --- /dev/null +++ b/test/options/__snapshots__/plugins.test.js.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Options plugins {Array} 1`] = ` +"export default \`
Hello
+\`" +`; + +exports[`Options plugins {Function} - {Array} 1`] = ` +"export default \`
Hello
+\`" +`; + +exports[`Options plugins {Function} - {Object} 1`] = ` +"export default \`
Hello
+\`" +`; diff --git a/test/options/config.test.js b/test/options/config.test.js new file mode 100644 index 0000000..5aecd0d --- /dev/null +++ b/test/options/config.test.js @@ -0,0 +1,50 @@ +'use strict' + +const webpack = require('../helpers/compiler') + +describe('Options', () => { + describe('config', () => { + test('path - {String}', () => { + const config = { + loader: { + test: /\.html$/, + options: { + config: { + path: 'test/fixtures/posthtml.config.js' + } + } + } + } + + return webpack('fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + + test('ctx - {Object}', () => { + const config = { + loader: { + test: /\.html$/, + options: { + config: { + path: 'test/fixtures/options/config/posthtml.config.js', + ctx: { plugin: true } + } + } + } + } + + return webpack('fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + }) +}) diff --git a/test/options/parser.test.js b/test/options/parser.test.js new file mode 100644 index 0000000..cc83546 --- /dev/null +++ b/test/options/parser.test.js @@ -0,0 +1,45 @@ +'use strict' + +const webpack = require('../helpers/compiler') + +describe('Options', () => { + describe('parser', () => { + test('{String}', () => { + const config = { + loader: { + test: /\.ssml$/, + options: { + parser: 'posthtml-sugarml' + } + } + } + + return webpack('options/parser/fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + + test('{Object}', () => { + const config = { + loader: { + test: /\.ssml$/, + options: { + parser: require('posthtml-sugarml')() + } + } + } + + return webpack('options/parser/fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + }) +}) diff --git a/test/options/plugins.test.js b/test/options/plugins.test.js new file mode 100644 index 0000000..36023e7 --- /dev/null +++ b/test/options/plugins.test.js @@ -0,0 +1,75 @@ +'use strict' + +const webpack = require('../helpers/compiler') + +describe('Options', () => { + describe('plugins', () => { + test('{Array}', () => { + const config = { + loader: { + test: /\.html$/, + options: { + ident: 'posthtml', + plugins: [ + require('../fixtures/plugin')() + ] + } + } + } + + return webpack('fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + + test('{Function} - {Array}', () => { + const config = { + loader: { + test: /\.html$/, + options: { + ident: 'posthtml', + plugins () { + return [ + require('../fixtures/plugin')() + ] + } + } + } + } + + return webpack('fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + + test('{Function} - {Object}', () => { + const config = { + loader: { + test: /\.html$/, + options: { + ident: 'posthtml', + plugins () { + return require('../fixtures/plugin')() + } + } + } + } + + return webpack('fixture.js', config) + .then((stats) => { + const module = stats.toJson().modules[1] + + expect(module.source).toMatchSnapshot() + }) + .catch((err) => err) + }) + }) +})