├── .gitignore ├── README.md ├── chompfile.toml ├── identifier.mjs ├── jspm-rollup.js ├── package.json ├── src └── package.json └── test ├── chunked.js ├── fixtures ├── babel.js ├── basic │ ├── dep.js │ ├── main.js │ ├── package.json │ └── test.ts ├── dynamic-import.js ├── lodash.js ├── package.json └── syntax-error.js ├── single.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | jspm_packages 2 | jspm.json 3 | test/out 4 | node_modules 5 | .vscode 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @jspm/plugin-rollup 2 | 3 | Standards-based JSPM Rollup plugin, including: 4 | 5 | * All module references as URLs 6 | * Support for resolving packages via node_modules or CDN providers 7 | * Fully compatible with Node.js resolution semantics 8 | * Support for import maps 9 | * Support for TypeScript 10 | 11 | ## Installation 12 | 13 | ```bash 14 | npm install @jspm/plugin-rollup rollup --save-dev 15 | ``` 16 | 17 | ## Usage 18 | 19 | rollup.config.js 20 | ```js 21 | import jspmRollup from '@jspm/plugin-rollup'; 22 | 23 | const baseUrl = new URL('./components', import.meta.url); 24 | 25 | export default { 26 | // Important to use "./" here to indicate a local path 27 | // and not a package. Resolved to baseUrl below. 28 | input: './main.js', 29 | plugins: [ 30 | jspmRollup({ 31 | baseUrl, 32 | 33 | // Generator options as per @jspm/generator 34 | defaultProvider: 'nodemodules', 35 | env: ['browser'], 36 | 37 | // map of externals to aliased or true 38 | externals: { 39 | react: 'custom-react' 40 | } 41 | }) 42 | ] 43 | } 44 | ``` 45 | 46 | ``` 47 | rollup -c 48 | ``` 49 | -------------------------------------------------------------------------------- /chompfile.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | 3 | [[task]] 4 | name = 'test' 5 | run = 'node test/test.js' 6 | # env = { JSPM_GENERATOR_LOG = '1' } 7 | -------------------------------------------------------------------------------- /identifier.mjs: -------------------------------------------------------------------------------- 1 | // Big ugly regular expressions that match characters in the 2 | // whitespace, identifier, and identifier-start categories. These 3 | // are only applied when a character is found to actually have a 4 | // code point above 128. 5 | // Generated by `bin/generate-identifier-regex.js`. 6 | let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u08a0-\u08b4\u08b6-\u08c7\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c88\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\u9ffc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7bf\ua7c2-\ua7ca\ua7f5-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc" 7 | let nonASCIIidentifierChars = "\u200c\u200d\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08d3-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf\u1ac0\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1df9\u1dfb-\u1dff\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f" 8 | 9 | const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]") 10 | const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]") 11 | 12 | nonASCIIidentifierStartChars = nonASCIIidentifierChars = null 13 | 14 | // These are a run-length and offset encoded representation of the 15 | // >0xffff code points that are a valid part of identifiers. The 16 | // offset starts at 0x10000, and each pair of numbers represents an 17 | // offset to the next range, and then a size of the range. They were 18 | // generated by bin/generate-identifier-regex.js 19 | 20 | // eslint-disable-next-line comma-spacing 21 | const astralIdentifierStartCodes = [0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,14,29,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,28,43,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,14,35,349,41,7,1,79,28,11,0,9,21,107,20,28,22,13,52,76,44,33,24,27,35,30,0,3,0,9,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,21,2,31,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,14,0,72,26,230,43,117,63,32,7,3,0,3,7,2,1,2,23,16,0,2,0,95,7,3,38,17,0,2,0,29,0,11,39,8,0,22,0,12,45,20,0,35,56,264,8,2,36,18,0,50,29,113,6,2,1,2,37,22,0,26,5,2,1,2,31,15,0,328,18,190,0,80,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,689,63,129,74,6,0,67,12,65,1,2,0,29,6135,9,1237,43,8,8952,286,50,2,18,3,9,395,2309,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,2357,44,11,6,17,0,370,43,1301,196,60,67,8,0,1205,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42717,35,4148,12,221,3,5761,15,7472,3104,541,1507,4938] 22 | 23 | // eslint-disable-next-line comma-spacing 24 | const astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,574,3,9,9,370,1,154,10,176,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,6,1,45,0,13,2,49,13,9,3,2,11,83,11,7,0,161,11,6,9,7,3,56,1,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,5,0,82,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,243,14,166,9,71,5,2,1,3,3,2,0,2,1,13,9,120,6,3,6,4,0,29,9,41,6,2,3,9,0,10,10,47,15,406,7,2,7,17,9,57,21,2,13,123,5,4,0,2,1,2,6,2,0,9,9,49,4,2,1,2,4,9,9,330,3,19306,9,135,4,60,6,26,9,1014,0,2,54,8,3,82,0,12,1,19628,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,262,6,10,9,419,13,1495,6,110,6,6,9,4759,9,787719,239] 25 | 26 | // This has a complexity linear to the value of the code. The 27 | // assumption is that looking up astral identifier characters is 28 | // rare. 29 | function isInAstralSet(code, set) { 30 | let pos = 0x10000 31 | for (let i = 0; i < set.length; i += 2) { 32 | pos += set[i] 33 | if (pos > code) return false 34 | pos += set[i + 1] 35 | if (pos >= code) return true 36 | } 37 | } 38 | 39 | // Test whether a given character code starts an identifier. 40 | 41 | function isIdentifierStart(code, astral) { 42 | if (code < 65) return code === 36 43 | if (code < 91) return true 44 | if (code < 97) return code === 95 45 | if (code < 123) return true 46 | if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)) 47 | if (astral === false) return false 48 | return isInAstralSet(code, astralIdentifierStartCodes) 49 | } 50 | 51 | // Test whether a given character is part of an identifier. 52 | 53 | function isIdentifierChar(code, astral) { 54 | if (code < 48) return code === 36 55 | if (code < 58) return true 56 | if (code < 65) return false 57 | if (code < 91) return true 58 | if (code < 97) return code === 95 59 | if (code < 123) return true 60 | if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)) 61 | if (astral === false) return false 62 | return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes) 63 | } 64 | 65 | function codePointLen (ch) { 66 | if (ch < 0x10000) return 1; 67 | return 2; 68 | } 69 | 70 | export function isIdentifier (str) { 71 | let pos = 0; 72 | let ch = str.codePointAt(pos); 73 | if (!isIdentifierStart(ch, true) || ch === '\\') 74 | return false; 75 | pos += codePointLen(ch); 76 | while (ch = str.codePointAt(pos)) { 77 | if (isIdentifierChar(ch, true)) { 78 | pos += codePointLen(ch); 79 | } 80 | else if (ch === '\\') { 81 | // no identifier escapes support for now 82 | return false; 83 | } 84 | else { 85 | break; 86 | } 87 | } 88 | return pos >= str.length; 89 | } 90 | -------------------------------------------------------------------------------- /jspm-rollup.js: -------------------------------------------------------------------------------- 1 | import { ImportMap } from '@jspm/import-map'; 2 | import babel from '@babel/core'; 3 | import dewTransformPlugin from 'babel-plugin-transform-cjs-dew'; 4 | import path from 'path'; 5 | import { Generator, fetch } from '@jspm/generator'; 6 | import * as cjsModuleLexer from 'cjs-module-lexer'; 7 | import { isIdentifier } from './identifier.mjs'; 8 | 9 | // TODO: 10 | // - ImportMap resolve to support env 11 | // - Generator to support outputting conditional import maps 12 | // - 13 | 14 | let cache = Object.create(null); 15 | let namedExportsCache = new Map(); 16 | await cjsModuleLexer.init(); 17 | 18 | function getCjsNamedExports (id, source, moduleFormats) { 19 | const cached = namedExportsCache.get(id); 20 | if (cached) 21 | return cached; 22 | const format = moduleFormats.get(id); 23 | 24 | // CJS -> ESM does not support named export module.exports = tracing 25 | if (format !== FORMAT_CJS && format !== FORMAT_CJS_DEW) 26 | return []; 27 | 28 | var exports, reexports; 29 | try { 30 | ({ exports, reexports } = cjsModuleLexer.parse(source)); 31 | } catch (e) { 32 | throw new Error('Lexer error parsing ' + id + ': ' + e.message); 33 | } 34 | const exportNames = new Set(exports); 35 | 36 | // Set first for cycles. 37 | namedExportsCache.set(id, exports); 38 | 39 | return [...exportNames].filter(expt => isIdentifier(expt)); 40 | 41 | // for (const reexport of reexports) { 42 | // const resolved = parentInfo.map[reexport]; 43 | // if (!resolved) 44 | // continue; 45 | // const external = cdn ? resolved.cdnExternal : resolved.external; 46 | // if (external) 47 | // continue; 48 | // const id = cdn ? dev && resolved.devId || resolved.cdnId : resolved.id; 49 | // const reexportNames = getCjsNamedExports(id); 50 | // for (const name of reexportNames) 51 | // exportNames.add(name); 52 | // } 53 | 54 | // return [...exportNames].filter(expt => isIdentifier(expt)); 55 | } 56 | 57 | function createDewNamedExportsBlock (cjsNamedExports, n, _, entry) { 58 | let exportsId = 'exports'; 59 | let i = 0; 60 | while (cjsNamedExports.includes(exportsId)) 61 | exportsId = 'exports' + ++i; 62 | let source = `const ${exportsId}${_}=${_}dew();${n}export default ${exportsId};${n}`; 63 | if (cjsNamedExports.length) { 64 | const aliased = []; 65 | for (const expt of cjsNamedExports) { 66 | if (expt === 'default' || expt === '__dew') 67 | continue; 68 | aliased.push(expt); 69 | } 70 | if (aliased.length) 71 | source += `var ${aliased.map(alias => `_$${alias}${_}=${_}${exportsId}['${alias}']`).join(`,${_}`)};${n}`; 72 | if (aliased.length || entry) 73 | source += `export${_}{${_}${aliased.map(alias => `_$${alias} as ${alias}`).join(`,${_}`)}${aliased.length && entry ? '' : ', '}${entry ? '' : 'dew as __dew'}${_}}${n}`; 74 | } 75 | return source; 76 | } 77 | 78 | const FORMAT_ESM = undefined; 79 | const FORMAT_CJS = 1; 80 | const FORMAT_CJS_DEW = 2; 81 | const FORMAT_JSON = 4; 82 | const FORMAT_TYPESCRIPT = 8; 83 | 84 | export default ({ baseUrl, defaultProvider = 'nodemodules', env = ['browser', 'development'], minify, externals, inputMap } = {}) => { 85 | if (baseUrl) { 86 | if (typeof baseUrl === 'string') 87 | baseUrl = new URL(baseUrl); 88 | } 89 | else { 90 | if (typeof Deno !== 'undefined') { 91 | baseUrl = new URL('file://' + Deno.cwd() + '/'); 92 | } 93 | else if (typeof process !== 'undefined' && process.versions.node) { 94 | baseUrl = new URL('file://' + process.cwd() + '/'); 95 | } 96 | else if (typeof document !== 'undefined') { 97 | const baseEl = document.querySelector('base[href]'); 98 | if (baseEl) 99 | baseUrl = new URL(baseEl.href + (baseEl.href.endsWith('/') ? '' : '/')); 100 | else if (typeof location !== 'undefined') 101 | baseUrl = new URL('../', new URL(location.href)); 102 | } 103 | } 104 | 105 | if (externals instanceof Array) { 106 | const _externals = {}; 107 | for (const ext of externals) 108 | _externals[ext] = true; 109 | externals = _externals; 110 | } 111 | 112 | let terser, generator, importMap, moduleFormats, externalsMap, 113 | bufferBuiltinResolved, moduleBuiltinResolved, processBuiltinResolved, builtinResolvedErr; 114 | 115 | return { 116 | name: '@jspm/plugin-rollup', 117 | options (opts) { 118 | opts.output = opts.output || {}; 119 | opts.output.interop = false; 120 | 121 | // Always convert the input into object form 122 | let input; 123 | if (Array.isArray(opts.input)) { 124 | const seen = {}; 125 | input = Object.fromEntries(opts.input.map(m => { 126 | let n = m.split('/').pop(); 127 | const extIndex = n.lastIndexOf('.'); 128 | if (extIndex !== -1) 129 | n = n.slice(0, extIndex); 130 | if (seen[n]) { 131 | const _n = n; 132 | let i = 1; 133 | while (seen[n]) 134 | n = _n + ++i; 135 | } 136 | seen[n] = true; 137 | return [n, m]; 138 | })); 139 | } 140 | else { 141 | input = opts.input; 142 | } 143 | opts.input = input; 144 | 145 | return opts; 146 | }, 147 | get logStream () { 148 | return generator.logStream; 149 | }, 150 | async buildStart (opts) { 151 | moduleFormats = new Map(); 152 | cache = Object.create(null); 153 | 154 | // run the generator phase _first_ 155 | generator = new Generator({ mapUrl: baseUrl, env, defaultProvider, inputMap, commonJS: true, typeScript: true }); 156 | 157 | if (minify && !terser) 158 | terser = await import('terser'); 159 | 160 | // always trace process and buffer builtins 161 | try { 162 | await Promise.all([generator.link('process'), generator.link('buffer'), generator.link('module')]); 163 | processBuiltinResolved = generator.importMap.resolve('process'); 164 | bufferBuiltinResolved = generator.importMap.resolve('buffer'); 165 | moduleBuiltinResolved = generator.importMap.resolve('module'); 166 | } 167 | catch (err) { 168 | builtinResolvedErr = err; 169 | } 170 | 171 | await Promise.all(Object.values(opts.input).map(async specifier => { 172 | await generator.link(specifier, baseUrl.href); 173 | })); 174 | 175 | // Pending next Generator update 176 | importMap = new ImportMap({ 177 | mapUrl: generator.importMap.mapUrl, 178 | map: generator.importMap.toJSON() 179 | }); 180 | 181 | if (externals) { 182 | externalsMap = new Map(); 183 | // resolve externals to populate externalsMap 184 | // TODO: support scoped externals 185 | await Promise.all(Object.entries(externals).map(async ([name, alias]) => { 186 | const resolved = importMap.resolve(name, baseUrl, { cache, env, browserBuiltins }); 187 | if (resolved !== null) 188 | externalsMap.set(resolved, alias); 189 | })); 190 | } 191 | }, 192 | async resolveId (name, parent) { 193 | const topLevel = !parent; 194 | if (topLevel) 195 | parent = baseUrl; 196 | 197 | const cjsResolve = moduleFormats.get(parent) & (FORMAT_CJS | FORMAT_CJS_DEW); 198 | 199 | if (cjsResolve && name[name.length - 1] === '/') 200 | name = name.substr(0, name.length - 1); 201 | 202 | let resolved = importMap.resolve(name, parent); 203 | 204 | if (!resolved) 205 | throw new Error('Module not found: ' + name + ' in ' + parent); 206 | 207 | if (resolved.endsWith('/')) 208 | throw new Error('Trailing slash resolution for ' + name + ' in ' + parent); 209 | if (resolved.endsWith('.')) 210 | throw new Error('Trailing dot resolution for ' + name + ' in ' + parent); 211 | 212 | if (resolved === null) { 213 | // non top-level not found treated as externals, but with a warning 214 | // if (err.code === 'MODULE_NOT_FOUND' && !topLevel && !name.startsWith('./') && !name.startsWith('../')) { 215 | // console.warn(`jspm could not find ${name} from ${parent}, treating as external.`); 216 | // return false; 217 | // } 218 | throw new Error('Module not found: ' + name + ' imported from ' + parent); 219 | } 220 | 221 | if (resolved.startsWith('node:')) 222 | return { id: resolved.slice(5), external: true }; 223 | 224 | const format = generator.getAnalysis(resolved).format; 225 | 226 | // builtins treated as externals 227 | // (builtins only emitted as builtins from resolver for Node, not browser) 228 | switch (format) { 229 | case 'json': 230 | moduleFormats.set(resolved, FORMAT_JSON); 231 | break; 232 | case 'commonjs': 233 | if (!cjsResolve) 234 | resolved += '?entry'; 235 | moduleFormats.set(resolved, cjsResolve ? FORMAT_CJS_DEW : FORMAT_CJS); 236 | break; 237 | case 'typescript': 238 | moduleFormats.set(resolved, FORMAT_TYPESCRIPT); 239 | break; 240 | } 241 | 242 | if (externalsMap) { 243 | let id = externalsMap.get(resolved); 244 | if (id !== undefined) { 245 | if (id === true) 246 | id = name; 247 | return { id, external: true }; 248 | } 249 | } 250 | 251 | return resolved; 252 | }, 253 | async load (id) { 254 | if (id.endsWith('?entry')) { 255 | const source = await (await fetch(id.slice(0, -6))).text(); 256 | return `import { dew } from "./${path.basename(id.slice(0, -6))}";` + 257 | '\n' + createDewNamedExportsBlock(getCjsNamedExports(id, source, moduleFormats), '\n', ' ', true); 258 | } 259 | return (await fetch(id)).text(); 260 | }, 261 | transform (code, id) { 262 | switch (moduleFormats.get(id)) { 263 | case FORMAT_ESM: 264 | return { code, map: null }; 265 | case FORMAT_JSON: 266 | return { code: 'export default ' + code, map: null }; 267 | case FORMAT_CJS: 268 | return { code, map: null }; 269 | case FORMAT_TYPESCRIPT: 270 | return babel.transform(code, { 271 | filename: id, 272 | babelrc: false, 273 | highlightCode: false, 274 | compact: false, 275 | sourceType: 'module', 276 | sourceMaps: true, 277 | parserOpts: { 278 | allowReturnOutsideFunction: true, 279 | // plugins: stage3Syntax 280 | }, 281 | presets: ['@babel/preset-typescript'] 282 | }); 283 | case FORMAT_CJS_DEW: 284 | // fallthrough 285 | break; 286 | default: 287 | throw new Error('Unexpected format'); 288 | } 289 | 290 | // FORMAT_CJS_DEW 291 | return babel.transform(code, { 292 | filename: id, 293 | babelrc: false, 294 | highlightCode: false, 295 | compact: false, 296 | sourceType: 'script', 297 | sourceMaps: true, 298 | parserOpts: { 299 | allowReturnOutsideFunction: true, 300 | // plugins: stage3Syntax 301 | }, 302 | plugins: [[dewTransformPlugin, { 303 | browserOnly: 'browser' in env, 304 | define: { 305 | 'process.env.NODE_ENV': 'production' in env ? '"production"' : '"dev"' 306 | }, 307 | resolve: (depId, opts) => { 308 | // CommonJS always gets trailing "/" stripped 309 | // Specifics of these resolution differences handled as best as possible in generator 310 | // Although packages using both / and non-/ forms have ambiguity 311 | if (depId.endsWith('/../') || depId === '../') { 312 | if (!(depId.startsWith('/') || depId.startsWith('./') || depId.startsWith('../'))) 313 | throw new Error('Unable to resolve ' + depId + ' in ' + id + ' as the final segment mapping is unknown.'); 314 | const resolved = new URL(depId, id).href; 315 | const lastSegmentIndex = resolved.lastIndexOf('/', resolved.length - 2); 316 | const lastSegment = resolved.slice(lastSegmentIndex, -1); 317 | depId += '..' + lastSegment; 318 | } 319 | else if (depId.endsWith('/')) { 320 | depId = depId.slice(0, -1); 321 | } 322 | 323 | if (depId === 'process') { 324 | if (builtinResolvedErr) 325 | throw builtinResolvedErr; 326 | return processBuiltinResolved; 327 | } 328 | if (depId === 'buffer') { 329 | if (builtinResolvedErr) 330 | throw builtinResolvedErr; 331 | return bufferBuiltinResolved; 332 | } 333 | if (depId === 'module') { 334 | if (builtinResolvedErr) 335 | throw builtinResolvedErr; 336 | return moduleBuiltinResolved; 337 | } 338 | 339 | if (opts.wildcard) { 340 | // we can only wildcard resolve internal requires 341 | if (!depId.startsWith('./') && !depId.startsWith('../')) 342 | return; 343 | const glob = new URL(depId, id); 344 | throw new Error(`TODO: CJS wildcard: ${glob}`); 345 | //const wildcardPath = path.relative(pkgBasePath, path.resolve(filePath.substr(0, filePath.lastIndexOf(path.sep)), pattern)).replace(/\\/g, '/'); 346 | //const wildcardPattern = new RegExp('^' + wildcardPath.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')); 347 | /*const matches = Object.keys(files).filter(file => file.match(wildcardPattern) && (file.endsWith('.js') || file.endsWith('.json') || file.endsWith('.node'))); 348 | const relFile = path.relative(pkgBasePath, path.resolve(filePath.substr(0, filePath.lastIndexOf(path.sep)))); 349 | return matches.map(match => { 350 | let relPath = path.relative(relFile, match).replace(/\\/g, '/'); 351 | if (relPath === '') 352 | relPath = './' + filePath.substr(filePath.lastIndexOf('/') + 1); 353 | else if (!relPath.startsWith('../')) 354 | relPath = './' + relPath; 355 | return relPath; 356 | });*/ 357 | } 358 | 359 | if (opts.optional) { 360 | const resolved = importMap.resolve(depId, id); 361 | if (!resolved) 362 | throw new Error('Could not resolve optional ' + depId + ' in ' + id); 363 | return depId; 364 | } 365 | 366 | return depId; 367 | }, 368 | wildcardExtensions: ['.js', '.json', '.node'], 369 | // externals are ESM dependencies 370 | esmDependencies: depId => { 371 | if (depId.endsWith('/../') || depId === '../') { 372 | if (!(depId.startsWith('/') || depId.startsWith('./') || depId.startsWith('../'))) 373 | throw new Error('Unable to resolve ' + depId + ' in ' + id + ' as the final segment mapping is unknown.'); 374 | const resolved = new URL(depId, id).href; 375 | const lastSegmentIndex = resolved.lastIndexOf('/', resolved.length - 2); 376 | const lastSegment = resolved.slice(lastSegmentIndex, -1); 377 | depId += '..' + lastSegment; 378 | } 379 | else if (depId.endsWith('/')) { 380 | depId = depId.slice(0, -1); 381 | } 382 | if (depId === 'buffer' || depId === 'module' || depId === 'process') 383 | return true; 384 | if (depId.endsWith('/')) depId = depId.slice(0, -1); 385 | const resolved = importMap.resolve(depId, id); 386 | if (!resolved) 387 | throw new Error('Could not resolve ' + depId + ' in ' + id); 388 | if (resolved.startsWith('node:')) 389 | return true; 390 | const { format } = generator.getAnalysis(resolved); 391 | return format === 'esm' || format === 'json' ? true : false; 392 | }, 393 | filename: `import.meta.url.startsWith('file:') ? decodeURI(import.meta.url.slice(7 + (typeof process !== 'undefined' && process.platform === 'win32'))) : new URL(import.meta.url).pathname`, 394 | dirname: `import.meta.url.startsWith('file:') ? decodeURI(import.meta.url.slice(0, import.meta.url.lastIndexOf('/')).slice(7 + (typeof process !== 'undefined' && process.platform === 'win32'))) : new URL(import.meta.url.slice(0, import.meta.url.lastIndexOf('/'))).pathname` 395 | }]] 396 | }); 397 | }, 398 | async renderChunk (code, _chunk, outputOptions) { 399 | if (!minify) return; 400 | try { 401 | var result = await terser.minify(code, { 402 | sourceMap: { 403 | asObject: true, 404 | }, 405 | module: true, 406 | toplevel: true, 407 | // defaults to mangle and compress 408 | keep_fnames: true, 409 | keep_classnames: true, 410 | compress: { 411 | defaults: false, 412 | 413 | // collapse_vars triples terser time... 414 | // collapse_vars: true, 415 | computed_props: true, 416 | conditionals: true, 417 | dead_code: true, 418 | directives: true, 419 | // switches: true, 420 | if_return: true, 421 | properties: true, 422 | 423 | side_effects: false, 424 | keep_fargs: true, 425 | keep_infinity: true, 426 | 427 | unused: true, 428 | evaluate: true 429 | }, 430 | output: { 431 | comments: function(_node, comment) { 432 | // multiline comment 433 | if (comment.type == "comment2") 434 | return /@(preserve|license|cc_on|param|returns|typedef|template|type|deprecated)/i.test(comment.value); 435 | } 436 | } 437 | }); 438 | } 439 | catch (e) { 440 | // Always skip Terser bugs 441 | this.warn({ 442 | message: `Terser error during build, skipping minification of chunk.\n${result.error}`, 443 | originalError: result.error 444 | }); 445 | return; 446 | } 447 | 448 | if (result.error) { 449 | // Always skip Terser bugs 450 | this.warn({ 451 | message: `Terser error during build, skipping minification of chunk.\n${result.error}`, 452 | originalError: result.error 453 | }); 454 | return; 455 | } 456 | 457 | return result; 458 | } 459 | }; 460 | }; 461 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jspm/plugin-rollup", 3 | "version": "1.1.0", 4 | "description": "Rollup plugin for jspm 2", 5 | "main": "jspm-rollup.js", 6 | "exports": "./jspm-rollup.js", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/jspm/rollup-plugin-jspm.git" 11 | }, 12 | "scripts": { 13 | "test": "node test/test.js" 14 | }, 15 | "files": [ 16 | "jspm-rollup.js", 17 | "identifier.mjs" 18 | ], 19 | "dependencies": { 20 | "@babel/core": "^7.25.2", 21 | "@babel/preset-typescript": "^7.24.7", 22 | "@jspm/core": "^2.0.0-beta.8", 23 | "@jspm/generator": "^2.4.1", 24 | "@jspm/import-map": "^1.1.0", 25 | "babel-plugin-transform-cjs-dew": "github:jspm/babel-plugin-transform-cjs-dew#main", 26 | "cjs-module-lexer": "^1.3.1", 27 | "terser": "^5.31.3", 28 | "typescript": "^5.5.4" 29 | }, 30 | "devDependencies": { 31 | "chalk": "^2.3.0", 32 | "lodash": "^4.17.4", 33 | "mocha": "^9.0.2", 34 | "rollup": "^4.20.0" 35 | }, 36 | "type": "module" 37 | } 38 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } -------------------------------------------------------------------------------- /test/chunked.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import fs from 'fs'; 3 | import * as rollup from 'rollup'; 4 | import jspmRollup from '@jspm/plugin-rollup'; 5 | import { fileURLToPath } from 'url'; 6 | 7 | const baseUrl = new URL('../', import.meta.url); 8 | const fixturesUrl = new URL('./fixtures', import.meta.url); 9 | const outFixturesUrl = new URL('./out', import.meta.url); 10 | const outFixturesPath = fileURLToPath(outFixturesUrl); 11 | 12 | suite('Syntax error messages', () => { 13 | test('Basic syntax error', async () => { 14 | try { 15 | await rollup.rollup({ 16 | input: [`${fixturesUrl}/syntax-error.js`], 17 | plugins: [jspmRollup()] 18 | }); 19 | } 20 | catch (err) { 21 | if (err.message.indexOf(`import a 'asdf`) === -1) 22 | assert(false); 23 | if (err.message.indexOf('^') === -1) 24 | assert(false); 25 | } 26 | }); 27 | }); 28 | 29 | suite('Dynamic import', () => { 30 | test('Dynamic import', async () => { 31 | const build = await rollup.rollup({ 32 | onwarn () {}, 33 | input: `${fixturesUrl}/dynamic-import.js`, 34 | plugins: [jspmRollup()] 35 | }); 36 | const { output: [{ code, map }] } = await build.generate({ format: 'es' }); 37 | assert.strictEqual(code.indexOf(`import('chalk')`), -1, 'Dynamic import must be remapped'); 38 | assert.deepStrictEqual(build.cache.modules.map(module => module.id.slice(baseUrl.href.length)).sort().filter(m => !m.startsWith('node_modules/@jspm/core/')), [ 39 | "node_modules/ansi-styles/index.js", 40 | "node_modules/chalk/index.js", 41 | "node_modules/chalk/index.js?entry", 42 | "node_modules/chalk/templates.js", 43 | "node_modules/color-convert/conversions.js", 44 | "node_modules/color-convert/index.js", 45 | "node_modules/color-convert/route.js", 46 | "node_modules/color-name/index.js", 47 | "node_modules/escape-string-regexp/index.js", 48 | "node_modules/supports-color/browser.js", 49 | 'test/fixtures/dynamic-import.js' 50 | ].map(path => path.endsWith('?dewexternal') ? path : path)); 51 | }); 52 | }); 53 | 54 | suite('Edge cases', () => { 55 | test.skip('@empty build', async () => { 56 | const bundle = await rollup.rollup({ 57 | input: 'x', 58 | plugins: [jspmRollup({ baseUrl: fixturesUrl, inputMap: { imports: { 'x': '@empty' } } })] 59 | }); 60 | await bundle.write({ 61 | file: `${outFixturesPath}/x.js`, 62 | format: 'esm' 63 | }); 64 | assert.strictEqual(fs.readFileSync(`${outFixturesPath}/x.js`).toString(), '\n'); 65 | }); 66 | }); 67 | 68 | suite('Browser builds', () => { 69 | test('babel', async () => { 70 | const bundle = await rollup.rollup({ 71 | onwarn () {}, 72 | input: `${fixturesUrl}/babel.js`, 73 | plugins: [jspmRollup({ env: ['browser'] })], 74 | }); 75 | bundle.write({ 76 | file: `${outFixturesPath}/babel-browser.js`, 77 | format: 'cjs' 78 | }); 79 | assert.ok(bundle.cache.modules.find(m => m.id.endsWith('@jspm/core/nodelibs/browser/process.js'))); 80 | }); 81 | }); 82 | 83 | suite('Node single file builds', () => { 84 | test('babel', async () => { 85 | const chunk = await rollup.rollup({ 86 | onwarn () {}, 87 | input: `${fixturesUrl}/babel.js`, 88 | plugins: [jspmRollup({ env: ['node', 'production'] })] 89 | }); 90 | 91 | await chunk.write({ format: 'esm', file: `${outFixturesPath}/babel.js` }); 92 | 93 | // Mocha screws up dynamic import for some reason 94 | await import(`${outFixturesUrl}/babel.js`); 95 | }); 96 | }); 97 | 98 | suite('Chunked builds', () => { 99 | test('babel / lodash chunk', async () => { 100 | const bundle = await rollup.rollup({ 101 | onwarn () {}, 102 | input: [ 103 | `${fixturesUrl}/babel.js`, 104 | `${fixturesUrl}/lodash.js` 105 | ], 106 | plugins: [jspmRollup({ env: ['node'] })] 107 | }); 108 | 109 | const { output: [babel, lodash] } = await bundle.write({ format: 'esm', dir: outFixturesPath }); 110 | 111 | assert.strictEqual(Object.keys(babel.modules).length, 306); 112 | assert.strictEqual(Object.keys(lodash.modules).length, 138); 113 | 114 | // test we can execute (assertions in code) 115 | await import(`${outFixturesUrl}/babel.js`); 116 | await import(`${outFixturesUrl}/lodash.js`); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/fixtures/babel.js: -------------------------------------------------------------------------------- 1 | import babel from '@babel/core'; 2 | import assert from 'assert'; 3 | 4 | const { code } = babel.transform(`export var p = 5;`); 5 | assert.equal(code, 'export var p = 5;'); 6 | -------------------------------------------------------------------------------- /test/fixtures/basic/dep.js: -------------------------------------------------------------------------------- 1 | require('path'); 2 | module.exports = path.resolve('dep'); 3 | -------------------------------------------------------------------------------- /test/fixtures/basic/main.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dep.js'); -------------------------------------------------------------------------------- /test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/basic/test.ts: -------------------------------------------------------------------------------- 1 | export var p: number = 5; 2 | -------------------------------------------------------------------------------- /test/fixtures/dynamic-import.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import('chalk').then(({ default: chalk }) => { 4 | assert.strictEqual(chalk.red('test'), ``); 5 | }); -------------------------------------------------------------------------------- /test/fixtures/lodash.js: -------------------------------------------------------------------------------- 1 | import sortBy from 'lodash/sortBy.js'; 2 | import assert from 'assert'; 3 | 4 | var users = [ 5 | { 'user': 'fred', 'age': 48 }, 6 | { 'user': 'barney', 'age': 36 }, 7 | { 'user': 'fred', 'age': 40 }, 8 | { 'user': 'barney', 'age': 34 } 9 | ]; 10 | 11 | assert(sortBy(users, [o => o.user])[0].user === 'barney'); 12 | -------------------------------------------------------------------------------- /test/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "esm", 3 | "map": { 4 | "x": "@empty" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/syntax-error.js: -------------------------------------------------------------------------------- 1 | import a 'asdf; -------------------------------------------------------------------------------- /test/single.js: -------------------------------------------------------------------------------- 1 | import * as rollup from 'rollup'; 2 | import jspmRollup from '@jspm/plugin-rollup'; 3 | import path from 'path'; 4 | import assert from 'assert'; 5 | 6 | suite('Basic Rollup', () => { 7 | const baseUrl = new URL('./fixtures/basic/', import.meta.url); 8 | 9 | test('Test', async () => { 10 | const bundle = await rollup.rollup({ 11 | input: './main.js', 12 | plugins: [jspmRollup({ baseUrl, env: ['browser'] })] 13 | }); 14 | 15 | const { output: [{ code, map: _map }] } = await bundle.generate({ format: 'esm' }); 16 | assert.strictEqual(eval(code.replace(/export \{ (\w+) as default \}/, '$1')), path.resolve('dep')); 17 | }); 18 | 19 | test('Test minify', async () => { 20 | const bundle = await rollup.rollup({ 21 | input: './main.js', 22 | plugins: [jspmRollup({ baseUrl, env: ['browser'], minify: true })] 23 | }); 24 | 25 | const { output: [{ code, map: _map }] } = await bundle.generate({ format: 'esm' }); 26 | assert.ok(code.length < 8000); 27 | assert.strictEqual(eval(code.replace(/export\{(\w+) as default\}/, '$1')), path.resolve('dep')); 28 | }); 29 | 30 | test('Test TypeScript', async () => { 31 | const bundle = await rollup.rollup({ 32 | input: './test.ts', 33 | plugins: [jspmRollup({ baseUrl, env: ['browser'] })] 34 | }); 35 | 36 | const { output: [{ code, map: _map }] } = await bundle.generate({ format: 'esm' }); 37 | assert.strictEqual(eval(code.replace(/export \{ (\w+) \}/, '$1')), 5); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import Mocha from 'mocha'; 2 | import { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import { promises as fsPromises } from 'fs'; 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)); 7 | const tests = (await fsPromises.readdir(__dirname)).reverse().filter(name => name.endsWith('.js') && !name.endsWith('test.js')); 8 | const mocha = new Mocha({ 9 | bail: true, 10 | ui: 'tdd', 11 | timeout: 30000 12 | }); 13 | 14 | for (const test of tests) { 15 | mocha.suite.emit('pre-require', global, test, mocha); 16 | await import('./' + test); 17 | } 18 | 19 | mocha.run(); 20 | --------------------------------------------------------------------------------