├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmrc ├── .travis.yml ├── .yarnrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── debug.js ├── enrich.js ├── index.js └── parser.js ├── test ├── enrich.test.js ├── fixtures │ └── Element.lit.js └── parser.test.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .nyc_output 4 | coverage 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | save-exact = true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | - '8' 5 | script: 6 | - yarn test:travis 7 | 8 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | save-exact true 2 | save-prefix false 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 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. 4 | 5 | ### [0.1.1](https://github.com/PolymerX/lit-loader/compare/v0.1.0...v0.1.1) (2019-09-13) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **package:** update dependencies ([8640ddf](https://github.com/PolymerX/lit-loader/commit/8640ddf)) 11 | * **package:** update dependencies ([#51](https://github.com/PolymerX/lit-loader/issues/51)) ([6a04177](https://github.com/PolymerX/lit-loader/commit/6a04177)) 12 | * **package:** update jscodeshift to version 0.6.4 ([166179e](https://github.com/PolymerX/lit-loader/commit/166179e)) 13 | * **package:** update postcss to version 7.0.16 ([43feba1](https://github.com/PolymerX/lit-loader/commit/43feba1)), closes [#41](https://github.com/PolymerX/lit-loader/issues/41) 14 | * **package:** update postcss to version 7.0.17 ([dd2cb1e](https://github.com/PolymerX/lit-loader/commit/dd2cb1e)) 15 | 16 | 17 | # [0.1.0](https://github.com/PolymerX/lit-loader/compare/v0.0.3...v0.1.0) (2019-01-22) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * **package:** update jscodeshift to version 0.6.0 ([7798a09](https://github.com/PolymerX/lit-loader/commit/7798a09)) 23 | * **package:** update jscodeshift to version 0.6.1 ([38088e1](https://github.com/PolymerX/lit-loader/commit/38088e1)) 24 | * **package:** update jscodeshift to version 0.6.2 ([10c21f2](https://github.com/PolymerX/lit-loader/commit/10c21f2)) 25 | * **package:** update loader-utils to version 1.2.1 ([070c6e3](https://github.com/PolymerX/lit-loader/commit/070c6e3)), closes [#13](https://github.com/PolymerX/lit-loader/issues/13) 26 | * **package:** update loader-utils to version 1.2.2 ([f6ae4b8](https://github.com/PolymerX/lit-loader/commit/f6ae4b8)) 27 | * **package:** update loader-utils to version 1.2.3 ([f368a67](https://github.com/PolymerX/lit-loader/commit/f368a67)) 28 | * **package:** update postcss to version 7.0.10 ([39b1540](https://github.com/PolymerX/lit-loader/commit/39b1540)) 29 | * **package:** update postcss to version 7.0.11 ([7ef9161](https://github.com/PolymerX/lit-loader/commit/7ef9161)) 30 | * **package:** update postcss to version 7.0.13 ([59b4fd8](https://github.com/PolymerX/lit-loader/commit/59b4fd8)), closes [#23](https://github.com/PolymerX/lit-loader/issues/23) 31 | * **package:** update postcss to version 7.0.6 ([1edb926](https://github.com/PolymerX/lit-loader/commit/1edb926)) 32 | * **package:** update postcss to version 7.0.7 ([2042cec](https://github.com/PolymerX/lit-loader/commit/2042cec)) 33 | * **package:** update postcss to version 7.0.8 ([6b8d2bf](https://github.com/PolymerX/lit-loader/commit/6b8d2bf)) 34 | * **package:** update postcss to version 7.0.9 ([306ce35](https://github.com/PolymerX/lit-loader/commit/306ce35)) 35 | 36 | 37 | 38 | 39 | ## [0.0.3](https://github.com/PolymerX/lit-loader/compare/v0.0.2...v0.0.3) (2018-10-11) 40 | 41 | 42 | 43 | 44 | ## [0.0.2](https://github.com/PolymerX/lit-loader/compare/v0.0.1...v0.0.2) (2018-07-17) 45 | 46 | 47 | ### Bug Fixes 48 | 49 | * **package:** update dependencies ([1f110eb](https://github.com/PolymerX/lit-loader/commit/1f110eb)) 50 | 51 | 52 | 53 | 54 | ## 0.0.1 (2018-05-18) 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) LasaleFamine (godev.space) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lit-loader 2 | [![Build Status](https://travis-ci.org/PolymerX/lit-loader.svg?branch=master)](https://travis-ci.org/PolymerX/lit-loader) [![codecov](https://codecov.io/gh/PolymerX/lit-loader/badge.svg?branch=master)](https://codecov.io/gh/PolymerX/lit-loader?branch=master) 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/PolymerX/lit-loader.svg)](https://greenkeeper.io/) 4 | 5 | [![npm](https://img.shields.io/npm/v/lit-loader.svg?style=for-the-badge)](https://github.com/PolymerX/lit-loader) 6 | 7 | > Single File Component LitElement loader for Webpack 8 | 9 | ## Example repository 10 | 11 | Checkout the working repository for a more comprehensive example: https://github.com/PolymerX/lit-loader-example 12 | 13 | ## Why 14 | 15 | Because we love separation of concerns also without separation of files! This loader will produce a Web Component using the [LitElement](https://github.com/Polymer/lit-element) starting from a Single File Component, [like Vue](https://vuejs.org/v2/guide/single-file-components.html). 16 | 17 | ## What 18 | 19 | The loader does a simple job: take your `.lit` file and build up as single `js` file. And you can easily use **PostCSS** on your styles. 20 | 21 | 22 | ## Install 23 | 24 | ``` 25 | $ yarn add --dev lit-loader 26 | ``` 27 | 28 | ## Usage 29 | 30 | #### Add to Webpack 31 | 32 | Add the loader to your Webpack conf `webpack.config.js`: 33 | 34 | ```js 35 | ... 36 | 37 | module: { 38 | rules: [ 39 | { 40 | test: /\.lit$/, 41 | loader: 'lit-loader' 42 | } 43 | ] 44 | } 45 | 46 | ... 47 | ``` 48 | 49 | #### Create your first `.lit` element 50 | 51 | `CounterElement.lit` 52 | ```html 53 | 61 | 62 | 72 | 73 | 101 | ``` 102 | 103 | #### Import it within another element and use it 104 | 105 | `index.js` 106 | ```js 107 | import {LitElement, html} from 'lit-element'; 108 | 109 | ... 110 | 111 | import './CounterElement.lit'; 112 | 113 | export default class MyApp extends LitElement { 114 | ... 115 | 116 | 117 | _render(props) { 118 | return html` 119 |
120 | 121 |
122 | ` 123 | } 124 | 125 | ... 126 | 127 | } 128 | 129 | ``` 130 | 131 | ## Use with Babel 132 | 133 | Just chain the `babel-loader` **AFTER** the `lit-loader` like so: 134 | 135 | ```js 136 | module: { 137 | rules: [ 138 | { 139 | test: /\.lit$/, 140 | use: ['babel-loader', 'lit-loader'] 141 | } 142 | ] 143 | } 144 | ``` 145 | 146 | ## PostCSS configuration 147 | 148 | You need to add a PostCSS configuration file (`postcss.config.js`) if you want to use it. 149 | 150 | ## Current status 151 | 152 | I think this should be considered experimental and I will try to improve it as much as I can. I really would love to accept some PR's to improve the project. 🤘 153 | 154 | 155 | ## License 156 | 157 | MIT © [LasaleFamine](https://godev.space) 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-loader", 3 | "version": "0.1.1", 4 | "description": "LitElement Single File Component loader for Webpack", 5 | "license": "MIT", 6 | "repository": "PolymerX/lit-loader", 7 | "bugs": { 8 | "url": "https://github.com/PolymerX/lit-loader/issues", 9 | "email": "info@godev.space" 10 | }, 11 | "homepage": "https://github.com/PolymerX/lit-loader", 12 | "author": { 13 | "name": "LasaleFamine ", 14 | "email": "info@godev.space", 15 | "url": "godev.space" 16 | }, 17 | "main": "src/index.js", 18 | "engines": { 19 | "node": ">=8" 20 | }, 21 | "scripts": { 22 | "release": "git pull && standard-version", 23 | "test": "xo && nyc ava test/*.test.js", 24 | "test:travis": "yarn test && nyc report --reporter=text-lcov > coverage.lcov && codecov" 25 | }, 26 | "files": [ 27 | "src" 28 | ], 29 | "keywords": [ 30 | "lit-loader", 31 | "LitElement", 32 | "lit-element-loader", 33 | "lit-element-postcss", 34 | "lit-element-babel", 35 | "lit-element-webpack", 36 | "polymer-lit" 37 | ], 38 | "dependencies": { 39 | "jscodeshift": "0.7.0", 40 | "loader-utils": "1.2.3", 41 | "parse5": "5.1.1", 42 | "postcss": "7.0.24", 43 | "postcss-load-config": "2.1.0" 44 | }, 45 | "devDependencies": { 46 | "ava": "2.4.0", 47 | "codecov": "3.6.1", 48 | "nyc": "15.0.0", 49 | "standard-version": "7.0.1", 50 | "xo": "0.24.0" 51 | }, 52 | "peerDependencies": { 53 | "webpack": "^4" 54 | }, 55 | "xo": { 56 | "rules": { 57 | "max-len": [ 58 | "error", 59 | { 60 | "code": 120, 61 | "ignoreUrls": true 62 | } 63 | ], 64 | "capitalized-comments": "off" 65 | } 66 | }, 67 | "nyc": { 68 | "reporter": [ 69 | "lcov", 70 | "text" 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/debug.js: -------------------------------------------------------------------------------- 1 | module.exports = msg => 2 | process.env.DEBUG === '*' || process.env.DEBUG === 'lit-loader' ? 3 | console.log('[DEBUG] lit-loader:', msg) : null; 4 | -------------------------------------------------------------------------------- /src/enrich.js: -------------------------------------------------------------------------------- 1 | const j = require('jscodeshift'); 2 | 3 | const renderTemplate = templateString => ` 4 | render() { 5 | return html\`${templateString}\` 6 | } 7 | `.replace(/\t/gi, ''); 8 | 9 | const addLitReferences = source => j(source) 10 | .find(j.ExportDefaultDeclaration) 11 | .insertBefore('import {LitElement, html} from \'lit-element\'') 12 | .find(j.Identifier) 13 | .at(0) 14 | .forEach(item => j(item).replaceWith(`${item.node.name} extends LitElement`)) 15 | .toSource(); 16 | 17 | const addRenderTemplate = (jsSource, templateString) => j(jsSource) 18 | .find(j.ClassDeclaration) 19 | .find(j.ClassBody) 20 | .find(j.MethodDefinition) 21 | .at(-1) 22 | .forEach(path => j(path).insertAfter(renderTemplate(templateString))) 23 | .toSource(); 24 | 25 | module.exports = (jsSource, templateString) => 26 | addRenderTemplate(addLitReferences(jsSource), templateString); 27 | 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const postcssrc = require('postcss-load-config'); 3 | 4 | const debug = require('./debug'); 5 | const parser = require('./parser'); 6 | const enrich = require('./enrich'); 7 | 8 | module.exports = async function (source) { 9 | const cb = this.async(); 10 | 11 | const {style, template, script} = parser(source); 12 | const isPostCSSEnable = style.attrs.find(item => item.name === 'lang') || {}; 13 | const css = isPostCSSEnable.value === 'postcss' ? await postcssrc() 14 | .then(({plugins, options}) => 15 | postcss(plugins).process(style.value, {...options, from: this.resourcePath})) 16 | .then(res => res.css) 17 | .catch(error => cb(error)) : style.value; 18 | 19 | const templateString = `${template.value}`; 20 | const enriched = enrich(script.value, templateString); 21 | 22 | debug(enriched); 23 | 24 | return cb(null, enriched); 25 | }; 26 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | const {parseFragment} = require('parse5'); 2 | const Serializer = require('parse5/lib/serializer'); 3 | 4 | const TYPES = ['style', 'script', 'template']; 5 | 6 | module.exports = text => { 7 | // don't change >, <, &, " to html entities in this context 8 | const coreEscape = Serializer.escapeString; 9 | Serializer.escapeString = str => str; 10 | 11 | const parsed = parseFragment(text) 12 | .childNodes 13 | .filter(node => TYPES.includes(node.nodeName)) 14 | .reduce((acc, node) => { 15 | const serializer = new Serializer(node.content); 16 | acc[node.nodeName] = { 17 | tag: node.nodeName, 18 | attrs: node.attrs, 19 | value: node.childNodes.length > 0 ? node.childNodes[0].value : serializer.serialize() 20 | }; 21 | return acc; 22 | }, {}); 23 | 24 | // restore entity escaping in case it's needed elsewhere 25 | Serializer.escapeString = coreEscape; 26 | 27 | return parsed; 28 | }; 29 | -------------------------------------------------------------------------------- /test/enrich.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import parser from '../src/parser'; 3 | import m from '../src/enrich'; 4 | 5 | import {SCRIPT, STYLE, TEMPLATE} from './fixtures/Element.lit'; 6 | 7 | test('Correctly enrich', t => { 8 | const content = ` 9 | 12 | 15 | 18 | `; 19 | 20 | const {style, template, script} = parser(content); 21 | const enriched = m(script.value, `${template.value}`); 22 | 23 | t.true(enriched.includes('import {LitElement, html} from \'lit-element\''), 'Includes import LitElement'); 24 | t.true(enriched.includes('CounterElement extends LitElement {'), 'Includes extends LitElement'); 25 | t.true(enriched.includes('render()'), 'Includes render function'); 26 | t.true(enriched.includes('return html`'), 'Includes return html function'); 27 | }); 28 | -------------------------------------------------------------------------------- /test/fixtures/Element.lit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/filename-case */ 2 | 3 | module.exports = { 4 | SCRIPT: ` 5 | export default class CounterElement { 6 | static get properties() { return { 7 | /* The total number of clicks you've done. */ 8 | clicks: Number, 9 | /* The current value of the counter. */ 10 | value: Number 11 | }}; 12 | 13 | constructor() { 14 | super(); 15 | this.clicks = 0; 16 | this.value = 0; 17 | } 18 | 19 | _onIncrement() { 20 | this.value++; 21 | this.clicks++; 22 | this.dispatchEvent(new CustomEvent('counter-incremented')); 23 | } 24 | 25 | _onDecrement() { 26 | this.value--; 27 | this.clicks++; 28 | this.dispatchEvent(new CustomEvent('counter-decremented')); 29 | } 30 | } 31 | 32 | window.customElements.define('counter-element', CounterElement); 33 | `.replace(/\n/gi, ''), 34 | STYLE: ` 35 | @import './style.pcss'; 36 | 37 | span { 38 | width: 20px; 39 | display: flex; 40 | text-align: center; 41 | font-weight: bold; 42 | }`.replace(/\n/gi, ''), 43 | TEMPLATE: ` 44 |
45 |

46 | ${() => ''} 47 | Clicked: \${props.clicks} times. 48 | Value is \${props.value}. 49 | 50 | 51 |

52 |
`.replace(/\n/gi, '') 53 | }; 54 | -------------------------------------------------------------------------------- /test/parser.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import m from '../src/parser'; 3 | 4 | import {SCRIPT, STYLE, TEMPLATE} from './fixtures/Element.lit'; 5 | 6 | test('Correctly parse content', t => { 7 | const content = ` 8 | 11 | 14 | 17 | `; 18 | 19 | const {style, template, script} = m(content); 20 | t.is(style.value.replace(/\n/gi, ''), STYLE); 21 | t.is(template.value.replace(/\n/gi, ''), TEMPLATE); 22 | t.is(script.value.replace(/\n/gi, ''), SCRIPT); 23 | }); 24 | --------------------------------------------------------------------------------