├── .npmignore ├── .gitignore ├── test ├── samples │ ├── main.less │ ├── main.js │ ├── test.less │ ├── test01.less │ └── index.html ├── rollup.build.css ├── dist.cjs.js └── demo.test.js ├── .babelrc ├── src ├── style.js └── index.js ├── rollup.config.js ├── .eslintrc ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /test/samples/main.less: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none !important; 3 | } -------------------------------------------------------------------------------- /test/samples/main.js: -------------------------------------------------------------------------------- 1 | import css from './test.less' 2 | import css01 from './test01.less' 3 | -------------------------------------------------------------------------------- /test/samples/test.less: -------------------------------------------------------------------------------- 1 | body{ 2 | .wrap{ 3 | color: #fff; 4 | } 5 | a{ 6 | font-size: 12px; 7 | } 8 | } -------------------------------------------------------------------------------- /test/samples/test01.less: -------------------------------------------------------------------------------- 1 | @import './main.less'; 2 | 3 | 4 | .content{ 5 | .header{ 6 | font-size: 12px; 7 | } 8 | .body{ 9 | color: #333; 10 | } 11 | } -------------------------------------------------------------------------------- /test/samples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/rollup.build.css: -------------------------------------------------------------------------------- 1 | body .wrap { 2 | color: #fff; 3 | } 4 | body a { 5 | font-size: 12px; 6 | } 7 | .hidden { 8 | display: none !important; 9 | } 10 | .content .header { 11 | font-size: 12px; 12 | } 13 | .content .body { 14 | color: #333; 15 | } 16 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | "transform-async-to-generator", 13 | "transform-runtime" 14 | ] 15 | } -------------------------------------------------------------------------------- /src/style.js: -------------------------------------------------------------------------------- 1 | /* 2 | * create a style tag and append to head tag 3 | * @params {String} css style 4 | */ 5 | 6 | function insertStyle ( css ) { 7 | if(!css) return ; 8 | 9 | if(typeof(window) == 'undefined') return ; 10 | let style = document.createElement('style'); 11 | style.setAttribute('media', 'screen'); 12 | 13 | style.innerHTML = css; 14 | document.head.appendChild(style); 15 | return css; 16 | } 17 | 18 | export { 19 | insertStyle 20 | } -------------------------------------------------------------------------------- /test/dist.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function __$styleInject(css) { 4 | if (!css) return; 5 | 6 | if (typeof window == 'undefined') return; 7 | var style = document.createElement('style'); 8 | style.setAttribute('media', 'screen'); 9 | 10 | style.innerHTML = css; 11 | document.head.appendChild(style); 12 | return css; 13 | } 14 | __$styleInject("body .wrap {\n color: #fff;\n}\nbody a {\n font-size: 12px;\n}\n"); 15 | 16 | __$styleInject(".hidden {\n display: none !important;\n}\n.content .header {\n font-size: 12px;\n}\n.content .body {\n color: #333;\n}\n"); -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | var pkg = require('./package.json') 3 | var external = Object.keys( pkg.dependencies ); 4 | 5 | export default { 6 | entry: 'src/index.js', 7 | plugins: [ 8 | babel({ 9 | exclude: 'node_modules/**', 10 | runtimeHelpers: true 11 | }) 12 | ], 13 | targets: [ 14 | { 15 | format: 'cjs', 16 | dest: pkg['main'] 17 | }, 18 | { 19 | format: 'es', 20 | dest: pkg['jsnext:main'] 21 | } 22 | ], 23 | external: external, 24 | sourceMap: false 25 | }; 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 2, "tab", { "SwitchCase": 1 } ], 4 | "quotes": [ 2, "single" ], 5 | "linebreak-style": [ 2, "unix" ], 6 | "semi": [ 2, "always" ], 7 | "keyword-spacing": [ 2, { "before": true, "after": true } ], 8 | "space-before-blocks": [ 2, "always" ], 9 | "space-before-function-paren": [ 2, "always" ], 10 | "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], 11 | "no-cond-assign": [ 0 ] 12 | }, 13 | "env": { 14 | "es6": true, 15 | "browser": true, 16 | "mocha": true, 17 | "node": true 18 | }, 19 | "extends": "eslint:recommended", 20 | "parserOptions": { 21 | "ecmaVersion": 6, 22 | "sourceType": "module" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/demo.test.js: -------------------------------------------------------------------------------- 1 | var assert = require( 'assert' ); 2 | var rollup = require( 'rollup' ); 3 | var less = require( '../dist/rollup-plugin-less.js' ); 4 | var npm = require( 'rollup-plugin-node-resolve' ); 5 | 6 | require( 'source-map-support' ).install(); 7 | 8 | process.chdir( __dirname ); 9 | 10 | function executeBundle ( bundle ) { 11 | var generated = bundle.generate(); 12 | var code = generated.code; 13 | 14 | var fn = new Function( 'assert', code ); 15 | fn( assert ); 16 | } 17 | 18 | describe( 'rollup-plugin-less', function () { 19 | it( 'converts less', function () { 20 | return rollup.rollup({ 21 | entry: 'samples/main.js', 22 | targets: [ 23 | { 24 | format: 'cjs', 25 | dest: './bundle.js' 26 | } 27 | ], 28 | plugins: [ less({ 29 | insert: true 30 | }) ] 31 | }).then((bundle) => { 32 | bundle.write({ 33 | dest: './dist.cjs.js', 34 | format: 'cjs' 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-plugin-less", 3 | "version": "1.1.3", 4 | "description": "a rollup plugin for less files", 5 | "main": "dist/rollup-plugin-less.js", 6 | "jsnext:main": "dist/rollup-plugin-less.mjs", 7 | "scripts": { 8 | "test": "mocha test/*.js", 9 | "pretest": "npm run build", 10 | "build": "rollup -c", 11 | "prebuild": "rm -rf dist/*" 12 | }, 13 | "keywords": [ 14 | "rollup", 15 | "plugin", 16 | "less", 17 | "rollup-plugin" 18 | ], 19 | "author": "yangxiaofu", 20 | "license": "ISC", 21 | "dependencies": { 22 | "babel-runtime": "^6.26.0", 23 | "fs-extra": "^0.30.0", 24 | "growl": ">=1.10.0", 25 | "less": "^3.13.1", 26 | "mkdirp": "^0.5.1", 27 | "rollup": "^0.34.7", 28 | "rollup-pluginutils": "^1.5.1" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/xiaofuzi/rollup-plugin-less" 33 | }, 34 | "devDependencies": { 35 | "assert": "^1.5.0", 36 | "babel-plugin-external-helpers": "^6.18.0", 37 | "babel-plugin-transform-async-to-generator": "^6.16.0", 38 | "babel-plugin-transform-runtime": "^6.15.0", 39 | "babel-preset-es2015": "^6.18.0", 40 | "eslint": "^3.2.2", 41 | "mocha": "^8.2.1", 42 | "rollup-plugin-babel": "^2.7.1", 43 | "rollup-plugin-node-resolve": "^2.0.0", 44 | "source-map-support": "^0.4.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | ```node 4 | npm install rollup-plugin-less --save 5 | ``` 6 | 7 | ## usage 8 | 9 | ```js 10 | import './test.less'; 11 | //generate css will be auto insert to the head tag if you set insert be true 12 | ``` 13 | 14 | ```js 15 | import { rollup } from 'rollup'; 16 | import less from 'rollup-plugin-less'; 17 | 18 | rollup({ 19 | entry: 'main.js', 20 | plugins: [ 21 | less() 22 | ] 23 | }); 24 | ``` 25 | 26 | 27 | ## Options 28 | 29 | ### insert 30 | 31 | + Default: `false` 32 | + Type: `Boolean` 33 | 34 | If you specify `true`, the plugin will insert compiled CSS into `` tag. 35 | 36 | ### output 37 | 38 | + Default: `rollup.build.css` 39 | 40 | + Type: `String|Function|Boolean` 41 | 42 | If you specify a string, it will be the path to write the generated CSS. 43 | If you specify a function, call it passing the generated CSS as an argument. 44 | If you specify a boolean, true will write the generated CSS to `rollup.build.css`, false won't write the file. 45 | 46 | ### include 47 | 48 | + Default: `[ '**/*.less', '**/*.css' ]` 49 | 50 | + Type: `String|Array` 51 | 52 | Minimatch pattern or array of minimatch patterns to determine which files are transpiled by the plugin. 53 | 54 | ### exclude 55 | 56 | + Default: `node_modules/**` 57 | 58 | + Type: `String|Array` 59 | 60 | Minimatch pattern or array of minimatch patterns to determine which files are explicitly not transpiled by the plugin, overrules the `include` option. 61 | 62 | ### option 63 | 64 | + Type: `Object` 65 | 66 | Options for [less](http://lesscss.org/usage/#programmatic-usage). 67 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { dirname } from 'path'; 3 | import less from 'less'; 4 | import { createFilter } from 'rollup-pluginutils'; 5 | import { insertStyle } from './style.js'; 6 | import mkdirp from 'mkdirp'; 7 | 8 | /** 9 | * Appends to a file even if its directory does not exist 10 | * @param {String} path the path of the file write to 11 | * @param {String} contents contents of file 12 | */ 13 | const appendToFile = (path, contents) => { 14 | return new Promise((resolve, reject) => { 15 | mkdirp(dirname(path), function (err) { 16 | if (err) { 17 | reject(err) 18 | } 19 | 20 | fs.appendFileSync(path, contents) 21 | resolve(); 22 | }); 23 | }); 24 | } 25 | 26 | let renderSync = (code, option) => { 27 | return less.render(code, option) 28 | .then(function (output) { 29 | return output.css; 30 | }, function (error) { 31 | throw error; 32 | }) 33 | }; 34 | 35 | let fileCount = 0; 36 | 37 | export default function plugin(options = {}) { 38 | options.insert = options.insert || false; 39 | const filter = createFilter(options.include || ['**/*.less', '**/*.css'], options.exclude || 'node_modules/**'); 40 | options.watch = options.watch || false; 41 | const injectFnName = '__$styleInject' 42 | return { 43 | name: 'less', 44 | intro() { 45 | return options.insert ? insertStyle.toString().replace(/insertStyle/, injectFnName) : ''; 46 | }, 47 | load() { 48 | if (options.watch){ 49 | fileCount = 0; 50 | } 51 | }, 52 | async transform(code, id) { 53 | if (!filter(id)) { 54 | return null; 55 | } 56 | fileCount++; 57 | 58 | try { 59 | options.option = options.option || {}; 60 | options.option['filename'] = id; 61 | options.output = options.output === undefined || options.output === true ? 'rollup.build.css' : options.output; 62 | if (options.plugins) { 63 | options.option['plugins'] = options.plugins 64 | } 65 | 66 | let css = await renderSync(code, options.option); 67 | 68 | if (options.output && isFunc(options.output)) { 69 | css = await options.output(css, id); 70 | } 71 | 72 | if (options.output && isString(options.output)) { 73 | if (fileCount == 1) { 74 | //clean the output file 75 | fs.removeSync(options.output); 76 | } 77 | await appendToFile(options.output, css); 78 | } 79 | 80 | let exportCode = ''; 81 | 82 | if (options.insert != false) { 83 | exportCode = `export default ${injectFnName}(${JSON.stringify(css.toString())});`; 84 | } else { 85 | exportCode = `export default ${JSON.stringify(css.toString())};`; 86 | } 87 | return { 88 | code: exportCode, 89 | map: { 90 | mappings: '' 91 | } 92 | }; 93 | } catch (error) { 94 | throw error; 95 | } 96 | } 97 | }; 98 | }; 99 | 100 | function isString(str) { 101 | if (typeof str == 'string') { 102 | return true; 103 | } else { 104 | return false; 105 | } 106 | } 107 | 108 | function isFunc(fn) { 109 | if (typeof fn == 'function') { 110 | return true; 111 | } else { 112 | return false; 113 | } 114 | } --------------------------------------------------------------------------------