├── .gitignore ├── .npmignore ├── .gitattributes ├── package.json ├── LICENSE.md ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | node_modules/ 3 | 4 | .DS_Store 5 | *.db 6 | *.bak 7 | *.tmp 8 | *.cmd 9 | ~* 10 | 11 | upload.py -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.settings 3 | /.project 4 | /.gitignore 5 | /node_modules 6 | /test 7 | /.tmp 8 | 9 | .DS_Store 10 | 11 | *.db 12 | *.bak 13 | *.tmp 14 | *.cmd 15 | ~* 16 | 17 | upload.py -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fis-postpackager-simple", 3 | "version": "0.0.26", 4 | "description": "a postpackager plugin for fis to auto replace pack resource and auto combine resources", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/hefangshi/fis-postpackager-simple" 12 | }, 13 | "keywords": [ 14 | "fis", 15 | "postpackager", 16 | "package", 17 | "simple" 18 | ], 19 | "author": "hefangshi", 20 | "license": "MIT", 21 | "dependencies": { 22 | "stable": "0.1.5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 baidu.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fis-postpackager-simple 2 | 3 | 用于自动打包页面零散资源和应用打包资源的[FIS](https://github.com/fex-team/fis/)插件 4 | 5 | ## 功能 6 | 7 | - 自动将页面中声明的资源引用替换为pack中设置的资源 8 | - 自动将未打包的零散资源按照引用顺序打包,默认关闭 9 | 10 | ## 用法 11 | 12 | $ npm install -g fis-postpackager-simple 13 | $ vi path/to/project/fis-conf.js #编辑项目配置文件 14 | 15 | ```javascript 16 | //file : path/to/project/fis-conf.js 17 | //使用simple插件,自动应用pack的资源引用 18 | fis.config.set('modules.postpackager', 'simple'); 19 | //开始autoCombine可以将零散资源进行自动打包 20 | fis.config.set('settings.postpackager.simple.autoCombine', true); 21 | //开启autoReflow使得在关闭autoCombine的情况下,依然会优化脚本与样式资源引用位置 22 | fis.config.set('settings.postpackager.simple.autoReflow', true); 23 | ``` 24 | 25 | ## 自动打包处理策略 26 | 27 | 开启了autoCombine后,为了保证资源引用顺序的正确,插件会自动调整脚本的加载位置 28 | 29 | - `````` 引用的脚本默认会在打包后移动到body底部 30 | - `````` 引用或声明的脚本会移动到head底部 **仅限pack打包,自动打包不适用** 31 | - `````` 引用或声明的脚本和样式不会进行自动打包 32 | - `````` 引用的样式表默认会在打包后移动到head底部 33 | - `````` 编写的内嵌脚本将会移动到body底部 34 | - `````` 声明的标签不会被处理 35 | - `````` 不会进行任何处理 36 | 37 | ## 配置项 38 | 39 | ### autoCombine 40 | 41 | 设置是否自动将零散资源进行打包,默认为 `false` 42 | 43 | ### autoReflow 44 | 45 | 设置是否自动优化脚本与样式资源引用位置,默认为 `false` 46 | 47 | ### fullPackHit 48 | 49 | 设置是否资源需要全部命中pack设置才会将整个资源包引用 50 | 51 | #### fullPackHit.js 52 | 53 | 默认为 `false` 54 | 55 | #### fullPackHit.css 56 | 57 | 默认为 `false` 58 | 59 | ### forceOutput 60 | 61 | autoCombine或autoReflow时是否对不包含head和body的页面强制输出合并脚本 62 | 63 | ### headTag 64 | 65 | autoCombine或autoReflow时自定义 `` 标记设置,如 ``。 66 | 67 | **注意** 替换完成后,headTag最终将不会被删除 68 | 69 | 70 | ### bodyTag 71 | 72 | autoCombine或autoReflow时自定义 `` 标记设置,如 ``。 73 | 74 | **注意** 替换完成后,bodyTag最终将不会被删除 75 | 76 | ### output 77 | 78 | 合成文件输出路径,默认值 "pkg/auto_combine_${hash}" ${hash}为合成内容hash值 ${index}为合成文件序列 79 | 80 | 81 | ## 适应范围 82 | 83 | 用于简单的Web前端项目自动打包减少页面请求连接数,同时可以通过[pack](https://github.com/fex-team/fis/wiki/%E9%85%8D%E7%BD%AEAPI#pack)设置来对公共资源进行独立打包。 84 | 85 | ## DEMO 86 | 87 | https://github.com/hefangshi/fis-quickstart-demo 88 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global fis */ 2 | 3 | /* 4 | * fis 5 | * http://fis.baidu.com/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var combineCount = 0; 11 | var combineCache = {}; 12 | var stable = require("stable"); 13 | var defaultSetting = { 14 | autoCombine: false, 15 | autoReflow: false, 16 | fullPackHit: { 17 | js: false, 18 | css: false 19 | }, 20 | headTag: "", 21 | bodyTag: "", 22 | output: 'pkg/auto_combine_${hash}' 23 | }; 24 | 25 | var placeHolders = {}; 26 | 27 | function trimQuery(url) { 28 | if (url.indexOf("?") !== -1) { 29 | url = url.slice(0, url.indexOf("?")); 30 | } 31 | return url; 32 | } 33 | 34 | 35 | function wrapTag(reg) { 36 | if (typeof reg === 'string') { 37 | return new RegExp(fis.util.escapeReg(reg)); 38 | } 39 | else if (!fis.util.is(reg, 'RegExp')) { 40 | fis.log.error('invalid regexp [' + reg + ']'); 41 | } 42 | return reg; 43 | } 44 | 45 | /** 46 | * 获取html页面中的 资源 47 | * 获取html页面中的 资源 48 | * 由于已经在标准流程之后,无需处理inline 49 | * 不需要改动页面中内嵌的样式 50 | * 需要将页面中内嵌的脚本移动到所有脚本的最下方 51 | * 需要去除注释内的引用 52 | * @param content 53 | * @param pathMap 54 | * @param usePlaceholder 55 | */ 56 | function analyzeHtml(content, pathMap, usePlaceholder) { 57 | var reg = 58 | /(|>))([\s\S]*?)(?:<\/script\s*>)\s*|<(link)\s+[\s\S]*?["'\s\w\/]>\s*|\s*/ig; 59 | var single, result; 60 | var resources = { 61 | scripts: [], 62 | inlineScripts: [], 63 | styles: [] 64 | }; 65 | var replaced = content.replace(reg, function (m, $1, $2, $3, $4) { 66 | var resourceID = null; 67 | var result = null; 68 | //$1为script标签, $2为内嵌脚本内容, $3为link标签, $4为注释内容 69 | if ($1) { 70 | //如果标签设置了data-fixed则不会收集此资源 71 | if (/(\sdata-fixed\s*=\s*)('true'|"true")/ig.test($1)) { 72 | return m; 73 | } 74 | var head = /(\sdata-position\s*=\s*)('head'|"head")/ig.test($1); 75 | result = $1.match(/(?:\ssrc\s*=\s*)(?:'([^']+)'|"([^"]+)"|[^\s\/>]+)/i); 76 | if (!result || !(result[1] || result[2])) { 77 | if (usePlaceholder) { 78 | return m; 79 | } 80 | // only process \n'; 392 | if (js.head) { 393 | headScripts += script; 394 | } 395 | else { 396 | scripts += script; 397 | } 398 | }); 399 | content = modBodyContent(content, scripts, settings); 400 | content = modHeadContent(content, headScripts, settings); 401 | return content; 402 | } 403 | 404 | function injectCss(cssList, content, ret, settings) { 405 | var styles = ''; 406 | cssList.forEach(function (css) { 407 | var uri; 408 | if (css.type === 'pkg') { 409 | uri = ret.map.pkg[css.id].uri; 410 | } 411 | else { 412 | uri = ret.map.res[css.id].uri; 413 | } 414 | styles += '\n'; 415 | }); 416 | content = modHeadContent(content, styles, settings); 417 | return content; 418 | } 419 | 420 | function injectInlineJs(inlineScripts, content, ret, settings) { 421 | var inlines = '', 422 | headInlines = ''; 423 | inlineScripts.forEach(function (script) { 424 | if (script.head) { 425 | headInlines += script.content; 426 | } 427 | else { 428 | inlines += script.content; 429 | } 430 | }); 431 | content = modBodyContent(content, inlines, settings); 432 | content = modHeadContent(content, headInlines, settings); 433 | return content; 434 | } 435 | 436 | function modHeadContent(content, mod, settings) { 437 | if (settings.headTag.test(content)) { 438 | content = content.replace(settings.headTag, mod + '$&'); 439 | } 440 | else if (settings.forceOutput) { 441 | content = mod + content; 442 | } 443 | return content; 444 | } 445 | 446 | function modBodyContent(content, mod, settings) { 447 | if (settings.bodyTag.test(content)) { 448 | content = content.replace(settings.bodyTag, mod + '$&'); 449 | } 450 | else if (settings.forceOutput) { 451 | content += mod; 452 | } 453 | return content; 454 | } 455 | 456 | function injectJsWithPlaceHolder(jsList, content, ret) { 457 | jsList.forEach(function (js) { 458 | var uri, id, file; 459 | if (js.type === 'pkg') { 460 | uri = ret.map.pkg[js.id].uri; 461 | file = ret.packMap.packToFile[js.id]; 462 | id = js.srcId; 463 | } 464 | else { 465 | uri = ret.map.res[js.id].uri; 466 | file = ret.src[js.id]; 467 | id = js.id; 468 | } 469 | var script = '\n'; 471 | content = content.replace(placeHolders[id], script); 472 | placeHolders[id] = false; 473 | }); 474 | return content; 475 | } 476 | 477 | function injectCssWithPlaceHolder(cssList, content, ret) { 478 | cssList.forEach(function (css) { 479 | var uri, id; 480 | if (css.type === 'pkg') { 481 | uri = ret.map.pkg[css.id].uri; 482 | id = css.srcId; 483 | } 484 | else { 485 | uri = ret.map.res[css.id].uri; 486 | id = css.id; 487 | } 488 | var style = '\n'; 489 | content = content.replace(placeHolders[id], style); 490 | placeHolders[id] = false; 491 | }); 492 | return content; 493 | } 494 | 495 | function cleanPlaceHolder(content) { 496 | fis.util.map(placeHolders, function (id, placeholder) { 497 | if (placeholder) { 498 | content = content.replace(placeholder, ''); 499 | } 500 | placeHolders[id] = false; 501 | }); 502 | return content; 503 | } 504 | 505 | 506 | module.exports = function (ret, conf, settings, opt) { //打包后处理 507 | if (!opt.pack) { 508 | return; 509 | } 510 | combineCache = {}; 511 | combineCount = 0; 512 | settings = fis.util.merge(fis.util.clone(defaultSetting), settings); 513 | settings.headTag = wrapTag(settings.headTag); 514 | settings.bodyTag = wrapTag(settings.bodyTag); 515 | var pathMap = getResourcePathMap(ret, conf, settings, opt); 516 | ret.packMap = getPackMap(ret, conf, settings, opt); 517 | // autoCombine模式下,autoReflow必为真 518 | if (settings.autoCombine) { 519 | settings.autoReflow = true; 520 | } 521 | fis.util.map(ret.src, function (subpath, file) { 522 | if (file.useCompile && file.isHtmlLike && file.noMapJs !== false) { // 类html文件 523 | placeHolders = {}; 524 | var content = file.getContent(); 525 | var result = analyzeHtml(content, pathMap, !settings.autoReflow); 526 | content = result.content; 527 | var jsList = getPkgResource(result.resources.scripts, ret, settings.fullPackHit.js); 528 | var cssList = getPkgResource(result.resources.styles, ret, settings.fullPackHit.css); 529 | if (settings.autoCombine) { 530 | jsList = autoCombine(jsList, ret, conf, settings, opt); 531 | cssList = autoCombine(cssList, ret, conf, settings, opt); 532 | } 533 | if (settings.autoReflow) { 534 | content = injectJs(jsList, content, ret, settings); 535 | content = injectCss(cssList, content, ret, settings); 536 | content = injectInlineJs(result.resources.inlineScripts, content, ret, settings); 537 | } 538 | else { 539 | content = injectJsWithPlaceHolder(jsList, content, ret); 540 | content = injectCssWithPlaceHolder(cssList, content, ret); 541 | content = cleanPlaceHolder(content); 542 | } 543 | file.setContent(content); 544 | if (file.useCache) { 545 | ret.pkg[file.subpath] = file; 546 | } 547 | } 548 | }); 549 | }; 550 | --------------------------------------------------------------------------------