├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── rollup.config.js ├── src └── index.js └── test ├── fixtures ├── define.js ├── file.js └── simple.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /test 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '7.6' 4 | deploy: 5 | provider: npm 6 | email: 86287344@qq.com 7 | skip_cleanup: true 8 | api_key: 9 | secure: RAjd4aFgPaPcpOKf/Prmd7mCAl1WE0USEp4kOXX4KxWKnQucOgsQJ1jrup3Ky1qJaUX1Uc+cMWo2r6nWBT5y1QKBtJWcijvdDx2L5wSkb/7rE/hnl/vAG10e1CrRqCjQZwvqX45hQPRfdxvEfdF2RYKrcPpLvYtDXH+9NA2qRNdQsJqvmAsy4LPGMVrHK/BYOGIjM6YI8BAFuzH2QzCmNj8V9v48PFqjIwAhPdlISoq4/oCgKrOnD8cvXj8PviQD5+n711x3uBiXQ+EANUwO3scqDWdVLb6txku1cxDNnepolosN0DdppIh60PJNMQp0/DbwaDqKT6N+1TX2shpYkvkhDxJ/PVbCg9i+PUaNmcqXn0REtQGyzqpDARZz5x9CVPFYvQEmFf6qVRT0yhEVrcd8woFgAweu93QfTdfDdONsP08nUS1FVcJOy580WSr1TOcw51nkPezszoTgoKqtgS8PqmuCBYAWETlgJeTl+y7V9TMXVt9oRbEHBf5NJi8wnuEdrwfayyOtNQS98yY7+2aM7B1j5b9uo96MQP44L1ux4WtqT+ulW82j9L1YzXW6/CDBTgvzom57289jJ7Hvq/UxpkxuegeUZmGxpls+CAU9KgIyxyrsMB6EgUWwmMA5KpTE7xTyJe/Uqnemnrxv0PlZcak9KviphXs+jLfJrYA= 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 jetiny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jetiny/rollup-plugin-re.svg?branch=master)](https://travis-ci.org/jetiny/rollup-plugin-re) 2 | 3 | # rollup-plugin-re 4 | 5 | Power rollup content transform plugin. 6 | 7 | ## Installation 8 | 9 | ``` 10 | npm install --save-dev rollup-plugin-re 11 | ``` 12 | 13 | ## Usage 14 | ```js 15 | import { rollup } from 'rollup' 16 | import replace from 'rollup-plugin-re' 17 | import commonjs from 'rollup-plugin-commonjs' 18 | rollup({ 19 | entry: 'main.js', 20 | plugins: [ 21 | replace({ 22 | // ... do replace before commonjs 23 | patterns: [ 24 | { 25 | // regexp match with resolved path 26 | match: /formidable(\/|\\)lib/, 27 | // string or regexp 28 | test: 'if (global.GENTLY) require = GENTLY.hijack(require);', 29 | // string or function to replaced with 30 | replace: '', 31 | } 32 | ] 33 | }), 34 | commonjs(), 35 | replace({ 36 | // ... do replace after commonjs 37 | }) 38 | ] 39 | }).then(...) 40 | ``` 41 | 42 | ## Define macro pre-processor 43 | 44 | use `defines` options to remove macro blocks 45 | 46 | ### Options 47 | ```javascript 48 | { 49 | defines: { 50 | IS_SKIP: false, 51 | IS_REMOVE: true, 52 | } 53 | } 54 | ``` 55 | 56 | ### input 57 | ```javascript 58 | // #if IS_SKIP 59 | console.log('!Skip!') 60 | // #endif 61 | // #if IS_REMOVE 62 | console.log('!Remove!') 63 | // #endif 64 | ``` 65 | 66 | ### output 67 | ```javascript 68 | // #if IS_SKIP 69 | console.log('!Skip!') 70 | // #endif 71 | ``` 72 | 73 | 74 | ## Replace 75 | 76 | use `replaces` options to quick replace text 77 | 78 | ### Options 79 | ```javascript 80 | { 81 | replaces: { 82 | $version: "1.0.1" 83 | } 84 | } 85 | ``` 86 | 87 | ### input 88 | ```javascript 89 | console.log('$version') 90 | ``` 91 | 92 | ### output 93 | ```javascript 94 | console.log('1.0.1') 95 | ``` 96 | 97 | 98 | ## Options 99 | 100 | ```javascript 101 | { 102 | // a minimatch pattern, or array of patterns, of files that 103 | // should be processed by this plugin (if omitted, all files 104 | // are included by default)... 105 | include: 'config.js', 106 | 107 | // ...and those that shouldn't, if `include` is otherwise 108 | // too permissive 109 | exclude: 'node_modules/**', 110 | defines: { 111 | IS_SKIP: false, 112 | IS_REMOVE: true, 113 | }, 114 | replaces: { 115 | $version: "1.0.1" 116 | }, 117 | patterns: [ 118 | { 119 | include: [], // same as above 120 | exclude: [], // same as above 121 | // regexp match with resolved path 122 | match: /formidable(\/|\\)lib/, 123 | // string or regexp 124 | test: 'if (global.GENTLY) require = GENTLY.hijack(require);', 125 | // string or function 126 | replace: '', 127 | }, 128 | // replace whole file content 129 | { 130 | text: 'exports = "content"', // replace content with given text 131 | }, 132 | { 133 | file: './replace.js', // replace with given relative file 134 | }, 135 | { 136 | transform (code, id) { // replace by function 137 | return `'use strict';\n${code}` 138 | } 139 | } 140 | ] 141 | } 142 | ``` 143 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-plugin-re", 3 | "version": "1.0.7", 4 | "description": "rollup replace plugin", 5 | "main": "dist/rollup-plugin-re.cjs.js", 6 | "jsnext": "dist/rollup-plugin-re.es.js", 7 | "scripts": { 8 | "build": "rollup -c -f cjs -o dist/rollup-plugin-re.cjs.js && rollup -c -o dist/rollup-plugin-re.es.js", 9 | "dev": "rollup -cw -f cjs -o dist/rollup-plugin-re.cjs.js", 10 | "test": "npm run lint && npm run build && npm run unit", 11 | "lint": "standard rollup.config.js test/test.js src/**", 12 | "format": "standard --fix test/** src/**", 13 | "unit": "cross-env NODE_ENV=production ava test/test.js", 14 | "prepublish": "npm run build" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/jetiny/rollup-plugin-re.git" 19 | }, 20 | "author": { 21 | "name": "jetiny", 22 | "email": "86287344@qq.com" 23 | }, 24 | "keywords": [ 25 | "rollup-plugin", 26 | "regexp", 27 | "replace" 28 | ], 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/jetiny/rollup-plugin-re/issues" 32 | }, 33 | "homepage": "https://github.com/jetiny/rollup-plugin-re#readme", 34 | "dependencies": { 35 | "magic-string": "^0.16.0", 36 | "rollup-pluginutils": "^2.0.1" 37 | }, 38 | "devDependencies": { 39 | "ava": "^0.16.0", 40 | "cross-env": "^3.1.3", 41 | "eslint-plugin-standard": "^2.0.1", 42 | "rollup": "^0.36.3", 43 | "rollup-plugin-buble": "^0.14.0", 44 | "standard": "^8.5.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from 'rollup-plugin-buble' 2 | 3 | const pack = require('./package.json') 4 | const YEAR = new Date().getFullYear() 5 | 6 | export default { 7 | entry: 'src/index.js', 8 | dest: 'dist/rollup-plugin-re.cjs.js', 9 | plugins: [ 10 | buble() 11 | ], 12 | banner () { 13 | return `/*! 14 | * ${pack.name} v${pack.version} 15 | * (c) ${YEAR} ${pack.author.name} ${pack.author.email} 16 | * Release under the ${pack.license} License. 17 | */` 18 | }, 19 | 20 | // Cleaner console 21 | onwarn (msg) { 22 | if (msg && msg.startsWith('Treating')) { 23 | return 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { createFilter } from 'rollup-pluginutils' 2 | import MagicString from 'magic-string' 3 | import {resolve} from 'path' 4 | import fs from 'fs' 5 | 6 | /* 7 | { 8 | defines: { 9 | IS_MOCK: true, 10 | } 11 | } 12 | */ 13 | function parseDefines (defines, patterns) { 14 | if (isObject(defines)) { 15 | for (let defineName in defines) { 16 | if (!defines[defineName]) { // remove define blocks 17 | patterns.push({ 18 | test: makeDefineRegexp(defineName), 19 | replace: '' 20 | }) 21 | } 22 | } 23 | } 24 | } 25 | 26 | /* 27 | { 28 | replaces: { 29 | Host: `'localhost'`, // replace 30 | } 31 | } 32 | */ 33 | function parseReplaces (replaces, patterns) { 34 | if (isObject(replaces)) { 35 | for (let replaceName in replaces) { 36 | patterns.push({ 37 | test: replaceName, 38 | replace: replaces[replaceName] 39 | }) 40 | } 41 | } 42 | } 43 | 44 | /* 45 | { 46 | patterns: [ 47 | { 48 | include: 'String|Regexp', 49 | exclude: 'String|Regexp', 50 | match: 'String|Regexp|Function', 51 | 52 | test: 'String|RegExp', 53 | 54 | replace: 'String|Function', 55 | file: 'String', 56 | text: 'String', 57 | 58 | transform: 'Function' 59 | } 60 | ] 61 | } 62 | */ 63 | function parsePatterns (patterns, contents) { 64 | patterns.forEach((it) => { 65 | if (it._pass) { 66 | return contents.push(it) 67 | } 68 | // filter 69 | it.filter = createFilter(it.include, it.exclude) 70 | 71 | // match 72 | if (isFunction(it.match)) { 73 | it.matcher = it.match 74 | } else if (isRegExp(it.match)) { 75 | it.matcher = it.match.test.bind(it.match) 76 | } else if (isString(it.match)) { 77 | it.matcher = createFilter(it.match) 78 | } 79 | // test 80 | if (isRegExp(it.test)) { 81 | it.testIsRegexp = true 82 | } else if (isString(it.test)) { 83 | it.testIsString = true 84 | } 85 | // replace 86 | if (isString(it.replace)) { 87 | it.replaceIsString = true 88 | } else if (isFunction(it.replace)) { 89 | it.replaceIsFunction = true 90 | } 91 | // content by file 92 | if (isString(it.file)) { 93 | it.replaceContent = (res) => { 94 | let file = resolve(res.id, '../', it.file) 95 | try { 96 | res.content = fs.readFileSync(file).toString() 97 | } catch (err) { 98 | throw new Error('[rollup-plugin-re] can not readFile: ' + file) 99 | } 100 | } 101 | } 102 | // text 103 | if (isString(it.text)) { 104 | it.replaceContent = (res) => { 105 | res.content = it.text 106 | } 107 | } 108 | contents.push(it) 109 | }) 110 | } 111 | 112 | function verbose (opts, result, id) { 113 | if (opts.verbose) { 114 | if (isFunction(opts.verbose)) { 115 | opts.verbose(result, id) 116 | } else { 117 | console.log(`[${result}]`, id) 118 | } 119 | } 120 | } 121 | 122 | export default function replace (options = {}) { 123 | const filter = createFilter(options.include, options.exclude) 124 | let contents = [] 125 | let patterns = options.patterns || (options.patterns = []) 126 | parseDefines(options.defines, patterns) 127 | parseReplaces(options.replaces, patterns) 128 | parsePatterns(patterns, contents) 129 | return { 130 | name: 're', 131 | transform (code, id) { 132 | if (!filter(id)) { 133 | verbose(options, 'exclude', id) 134 | return 135 | } 136 | if (!contents.length) { 137 | verbose(options, 'ignore', id) 138 | return 139 | } 140 | let hasReplacements = false 141 | let magicString = new MagicString(code) 142 | contents.forEach((pattern) => { 143 | if (!pattern.filter(id)) { 144 | return 145 | } 146 | if (pattern.matcher && !pattern.matcher(id)) { 147 | return 148 | } 149 | // replace content 150 | if (pattern.replaceContent) { 151 | let res = { 152 | id, 153 | code, 154 | magicString 155 | } 156 | pattern.replaceContent(res) 157 | if (isString(res.content) && res.content !== code) { 158 | hasReplacements = true 159 | magicString = new MagicString(res.content) 160 | code = res.content 161 | } 162 | } 163 | // transform 164 | if (isFunction(pattern.transform)) { 165 | let newCode = pattern.transform(code, id) 166 | if (isString(newCode) && newCode !== code) { 167 | hasReplacements = true 168 | magicString = new MagicString(newCode) 169 | code = newCode 170 | } 171 | } 172 | // test & replace 173 | if (pattern.testIsRegexp) { 174 | let match = pattern.test.exec(code) 175 | let start, end 176 | while (match) { 177 | hasReplacements = true 178 | start = match.index 179 | end = start + match[0].length 180 | let str 181 | if (pattern.replaceIsString) { 182 | // fill capture groups 183 | str = pattern.replace.replace(/\$\$|\$&|\$`|\$'|\$\d+/g, m => { 184 | if (m === '$$') { 185 | return '$' 186 | } 187 | if (m === '$&') { 188 | return match[0] 189 | } 190 | if (m === '$`') { 191 | return code.slice(0, start) 192 | } 193 | if (m === "$'") { 194 | return code.slice(end) 195 | } 196 | const n = +m.slice(1) 197 | if (n >= 1 && n < match.length) { 198 | return match[n] || '' 199 | } 200 | return m 201 | }) 202 | } else { 203 | str = pattern.replace.apply(null, match) 204 | } 205 | if (!isString(str)) { 206 | throw new Error('[rollup-plugin-re] replace function should return a string') 207 | } 208 | magicString.overwrite(start, end, str) 209 | match = pattern.test.global ? pattern.test.exec(code) : null 210 | } 211 | } else if (pattern.testIsString) { 212 | let start, end 213 | let len = pattern.test.length 214 | let pos = code.indexOf(pattern.test) 215 | while (pos !== -1) { 216 | hasReplacements = true 217 | start = pos 218 | end = start + len 219 | if (pattern.replaceIsString) { 220 | magicString.overwrite(start, end, pattern.replace) 221 | } else if (pattern.replaceIsFunction) { 222 | let str = pattern.replace(id) 223 | if (!isString(str)) { 224 | throw new Error('[rollup-plugin-re] replace function should return a string') 225 | } 226 | magicString.overwrite(start, end, str) 227 | } 228 | pos = code.indexOf(pattern.test, pos + 1) 229 | } 230 | } 231 | }) 232 | 233 | if (!hasReplacements) { 234 | return 235 | } 236 | verbose(options, 'replace', id) 237 | let result = { code: magicString.toString() } 238 | if (options.sourceMap !== false) { 239 | result.map = magicString.generateMap({ hires: true }) 240 | } 241 | return result 242 | } 243 | } 244 | } 245 | 246 | let source = /\/\/\s#if\sIS_DEFINE(.*)([\s\S]*?)\/\/\s#endif/g.source 247 | 248 | function makeDefineRegexp (text) { 249 | return new RegExp(source.replace('IS_DEFINE', text), 'g') 250 | } 251 | 252 | function isRegExp (re) { 253 | return Object.prototype.toString.call(re) === '[object RegExp]' 254 | } 255 | 256 | function isString (str) { 257 | return typeof str === 'string' 258 | } 259 | 260 | function isFunction (val) { 261 | return typeof val === 'function' 262 | } 263 | 264 | function isObject (val) { 265 | return val !== null && Object.prototype.toString.call(val) === '[object Object]' 266 | } 267 | -------------------------------------------------------------------------------- /test/fixtures/define.js: -------------------------------------------------------------------------------- 1 | 2 | // #if IS_HELLO 3 | console.log('!HelloWorld!') 4 | // #endif 5 | 6 | // #if IS_SKIP 7 | console.log('!Skip!') 8 | // #endif 9 | 10 | // #if IS_BYE 11 | // bye 12 | 13 | console.log('!GoodBye!') 14 | 15 | // bye 16 | // #endif 17 | 18 | // #if IS_HELLO 19 | 20 | console.log('!HelloWorld2!') 21 | 22 | // #endif 23 | 24 | // #if IS_SKIP 25 | console.log('!Skip2!') 26 | // #endif 27 | 28 | // #if IS_BYE 29 | console.log('!GoodBye2!') 30 | // #endif 31 | -------------------------------------------------------------------------------- /test/fixtures/file.js: -------------------------------------------------------------------------------- 1 | exports.file = 'fileContent' -------------------------------------------------------------------------------- /test/fixtures/simple.js: -------------------------------------------------------------------------------- 1 | const DEBUG = !(process.env.NODE_ENV === 'production') 2 | console.log(DEBUG) 3 | console.log(', )---, ) issues 1 #') 4 | console.log('!HelloWorld!') 5 | console.log(swap(a, b)) 6 | console.log('once once') -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import test from 'ava' 4 | import {rollup} from 'rollup' 5 | import replace from '..' 6 | 7 | test('replaces strings', assert => rollup({ 8 | entry: 'fixtures/simple.js', 9 | plugins: [ 10 | replace({ 11 | patterns: [ 12 | { 13 | test: 'process.env.NODE_ENV', 14 | replace: "'production'" 15 | }, 16 | { 17 | test: /,\s*\)/g, 18 | replace: ')' 19 | }, 20 | { 21 | test: /!(\w+)!/g, 22 | replace: function (_, words) { 23 | return words.toLowerCase() 24 | } 25 | }, 26 | { 27 | test: /swap\((\w+), (\w+)\)/g, 28 | replace: '$2, $1' 29 | }, 30 | { 31 | test: /once/, 32 | replace: '1' 33 | } 34 | ] 35 | }) 36 | ] 37 | }).then((bundle) => { 38 | const code = bundle.generate().code 39 | assert.true(code.indexOf("'production' === 'production'") !== -1) 40 | assert.true(code.indexOf(', )') === -1) 41 | assert.true(!!~~code.indexOf('helloworld')) 42 | assert.true(code.indexOf('console.log(b, a)') !== -1) 43 | assert.true(code.indexOf("console.log('1 once')") !== -1) 44 | })) 45 | 46 | test('replaces with text', assert => rollup({ 47 | entry: 'fixtures/simple.js', 48 | plugins: [ 49 | replace({ 50 | patterns: [ 51 | { 52 | text: `exports = 'xxx'` 53 | } 54 | ] 55 | }) 56 | ] 57 | }).then((bundle) => { 58 | const code = bundle.generate().code 59 | assert.true(code.indexOf('xxx') !== -1) 60 | })) 61 | 62 | test('replaces with file', assert => rollup({ 63 | entry: 'fixtures/simple.js', 64 | plugins: [ 65 | replace({ 66 | patterns: [ 67 | { 68 | file: 'file.js' 69 | } 70 | ] 71 | }) 72 | ] 73 | }).then((bundle) => { 74 | const code = bundle.generate().code 75 | assert.true(code.indexOf('fileContent') !== -1) 76 | })) 77 | 78 | test('defines', assert => rollup({ 79 | entry: 'fixtures/define.js', 80 | plugins: [ 81 | replace({ 82 | defines: { 83 | IS_HELLO: true, 84 | IS_BYE: false 85 | } 86 | }) 87 | ] 88 | }).then((bundle) => { 89 | const code = bundle.generate().code 90 | assert.true(code.indexOf('!Skip!') !== -1) 91 | assert.true(code.indexOf('!Skip2!') !== -1) 92 | assert.true(code.indexOf('!HelloWorld!') !== -1) 93 | assert.true(code.indexOf('!HelloWorld2!') !== -1) 94 | assert.true(code.indexOf('!GoodBye!') === -1) 95 | assert.true(code.indexOf('!GoodBye2!') === -1) 96 | })) 97 | 98 | test('replaces', assert => rollup({ 99 | entry: 'fixtures/define.js', 100 | plugins: [ 101 | replace({ 102 | replaces: { 103 | IS_SKIP: 'IS_NO_SKIP', 104 | IS_BYE: 'IS_NO_BYE' 105 | } 106 | }) 107 | ] 108 | }).then((bundle) => { 109 | const code = bundle.generate().code 110 | assert.true(code.indexOf('IS_NO_SKIP') !== -1) 111 | assert.true(code.indexOf('IS_NO_BYE') !== -1) 112 | assert.true(code.indexOf('IS_SKIP') === -1) 113 | assert.true(code.indexOf('IS_BYE') === -1) 114 | })) 115 | 116 | test('replaces with file', assert => rollup({ 117 | entry: 'fixtures/simple.js', 118 | plugins: [ 119 | replace({ 120 | patterns: [ 121 | { 122 | file: './file.js', 123 | transform (code) { 124 | return code + `\ndebugger;` 125 | } 126 | } 127 | ] 128 | }) 129 | ] 130 | }).then((bundle) => { 131 | const code = bundle.generate().code 132 | assert.true(code.indexOf('fileContent') !== -1) 133 | assert.true(code.indexOf('debugger;') !== -1) 134 | })) 135 | 136 | test('verbose', assert => { 137 | let ids = [] 138 | return rollup({ 139 | entry: 'fixtures/simple.js', 140 | plugins: [ 141 | replace({ 142 | verbose (it) { 143 | ids.push(it) 144 | } 145 | }) 146 | ] 147 | }).then((bundle) => { 148 | assert.true(ids.length === 1) 149 | }) 150 | }) 151 | 152 | --------------------------------------------------------------------------------