diff --git a/.eslintrc.json b/.eslintrc.json index 99af1fe5342..d3c9e011a87 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,5 +13,8 @@ "globals": { "parcelRequire": true, "define": true + }, + "rules": { + "no-return-await": "error" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7d382d42e..7e904a13617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and parcel adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - Add support for HMR with elm-hot [Details](https://github.com/parcel-bundler/parcel/pull/2388) - Log dev server access for log level verbose or more [Details](https://github.com/parcel-bundler/parcel/pull/2402) - Process array of assets for JSON-LD [Details](https://github.com/parcel-bundler/parcel/pull/2319) -- Extract workerfarm into seperate package [Details](https://github.com/parcel-bundler/parcel/pull/2162) +- Extract workerfarm into separate package [Details](https://github.com/parcel-bundler/parcel/pull/2162) - Extract Logger into its own package [Details](https://github.com/parcel-bundler/parcel/pull/2165) - Extract watcher into its own package [Details](https://github.com/parcel-bundler/parcel/pull/2176) - Merge fs-watcher-child into Parcel's monorepo [Details](https://github.com/parcel-bundler/parcel/pull/2197) @@ -538,7 +538,7 @@ and parcel adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - Opencollective [Details](https://github.com/parcel-bundler/parcel/commit/0f554dc2f5c8f2557ec84eee5301b90ffb279764) - Use `JSON5` to parse config files [Details](https://github.com/parcel-bundler/parcel/commit/bd458660ce38e7a1d25bd9758084acc24418e054) -- Move JSAsset options gathering into seperate function [Details](https://github.com/parcel-bundler/parcel/commit/333c3aa5d20f98a5f3c52635751032d12854c13c) +- Move JSAsset options gathering into separate function [Details](https://github.com/parcel-bundler/parcel/commit/333c3aa5d20f98a5f3c52635751032d12854c13c) ### Fixed diff --git a/README.md b/README.md index 68c4d2000f0..cbe0d6d3fd2 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ or with npm: npm install -g parcel-bundler ``` -2. Parcel can take any type of file as an entry point, but an HTML or JavaScript file is a good place to start. If you link your main JavaScript file in the HTML using a relative path, Parcel will also process it for you, and replace the reference with a URL to the output file. +2. Parcel can take any type of file as an entry point, but a HTML or JavaScript file is a good place to start. If you link your main JavaScript file in the HTML using a relative path, Parcel will also process it for you, and replace the reference with a URL to the output file. ```html @@ -46,7 +46,7 @@ npm install -g parcel-bundler ``` -3. Parcel has a development server built in, which will automatically rebuild your app as you change files and supports hot module replacement for fast development. Just point it at your entry file: +3. Parcel has a development server built in which will automatically rebuild your app as you change files and supports hot module replacement for fast development. Just point it at your entry file: ```shell parcel index.html diff --git a/packages/core/fs/package.json b/packages/core/fs/package.json index 8c7ae89f88b..05489f28cd7 100644 --- a/packages/core/fs/package.json +++ b/packages/core/fs/package.json @@ -11,6 +11,9 @@ "engines": { "node": ">= 6.0.0" }, + "publishConfig": { + "access": "public" + }, "scripts": { "test": "echo this package has no tests yet", "test-ci": "yarn build && yarn test", diff --git a/packages/core/integration-tests/test/bundler.js b/packages/core/integration-tests/test/bundler.js index 7534f3a5603..3203dcca046 100644 --- a/packages/core/integration-tests/test/bundler.js +++ b/packages/core/integration-tests/test/bundler.js @@ -1,7 +1,12 @@ const assert = require('assert'); const sinon = require('sinon'); const path = require('path'); -const {assertBundleTree, bundle, bundler, nextBundle} = require('./utils'); +const { + assertBundleTree, + bundle, + bundler, + nextBundle +} = require('@parcel/test-utils'); describe('bundler', function() { it('should bundle once before exporting middleware', async function() { diff --git a/packages/core/integration-tests/test/contentHashing.js b/packages/core/integration-tests/test/contentHashing.js index 9dc279db394..584677c07c9 100644 --- a/packages/core/integration-tests/test/contentHashing.js +++ b/packages/core/integration-tests/test/contentHashing.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, rimraf, ncp} = require('./utils'); +const {bundle, rimraf, ncp} = require('@parcel/test-utils'); describe('content hashing', function() { beforeEach(async function() { diff --git a/packages/core/integration-tests/test/css.js b/packages/core/integration-tests/test/css.js index 44be2354224..d55db7e807f 100644 --- a/packages/core/integration-tests/test/css.js +++ b/packages/core/integration-tests/test/css.js @@ -1,7 +1,13 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, run, assertBundleTree, rimraf, ncp} = require('./utils'); +const { + bundle, + run, + assertBundleTree, + rimraf, + ncp +} = require('@parcel/test-utils'); describe('css', function() { it('should produce two bundles when importing a CSS file', async function() { @@ -12,12 +18,16 @@ describe('css', function() { assets: ['index.js', 'index.css', 'local.js', 'local.css'], childBundles: [ { - name: 'index.map' + name: 'index.js.map' }, { name: 'index.css', assets: ['index.css', 'local.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -44,21 +54,30 @@ describe('css', function() { ], childBundles: [ { + type: 'css', name: 'index.css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { - type: 'map' + name: 'index.js.map' }, { type: 'js', - assets: ['local.js', 'local.css'], + assets: ['local.css', 'local.js'], childBundles: [ { type: 'css', assets: ['local.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -85,10 +104,14 @@ describe('css', function() { { name: 'index.css', assets: ['index.css', 'other.css', 'local.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { - name: 'index.map', + name: 'index.js.map', type: 'map' } ] @@ -118,7 +141,11 @@ describe('css', function() { { name: 'index.css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -173,7 +200,11 @@ describe('css', function() { { name: 'index.css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -213,6 +244,54 @@ describe('css', function() { ); }); + it('should support linking to assets in parent folders with url() from CSS', async function() { + let b = await bundle( + [ + path.join(__dirname, '/integration/css-url-relative/src/a/style1.css'), + path.join(__dirname, '/integration/css-url-relative/src/b/style2.css') + ], + { + production: true, + sourceMaps: false + } + ); + + await assertBundleTree(b, [ + { + type: 'css', + assets: ['style1.css'], + childBundles: [ + { + type: 'png' + } + ] + }, + { + type: 'css', + assets: ['style2.css'] + } + ]); + + let css = await fs.readFile( + path.join(__dirname, '/dist/a/style1.css'), + 'utf8' + ); + + assert(css.includes('background-image'), 'includes `background-image`'); + assert(/url\([^)]*\)/.test(css), 'includes url()'); + + assert( + await fs.exists( + path.join( + __dirname, + path.dirname('/dist/a/style1.css'), + css.match(/url\(([^)]*)\)/)[1] + ) + ), + 'path specified in url() exists' + ); + }); + it('should support transforming with postcss', async function() { let b = await bundle(path.join(__dirname, '/integration/postcss/index.js')); @@ -223,7 +302,11 @@ describe('css', function() { { name: 'index.css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -235,9 +318,9 @@ describe('css', function() { assert.equal(typeof output, 'function'); let value = output(); - assert(/_index_[0-9a-z]+_1/.test(value)); + assert(/_index_[0-9a-z]/.test(value)); - let cssClass = value.match(/(_index_[0-9a-z]+_1)/)[1]; + let cssClass = value.match(/(_index_[0-9a-z]+)/)[1]; let css = await fs.readFile( path.join(__dirname, '/dist/index.css'), @@ -259,6 +342,227 @@ describe('css', function() { assert.equal(run1(), run2()); }); + it('should support postcss composes imports', async function() { + let b = await bundle( + path.join(__dirname, '/integration/postcss-composes/index.js') + ); + + await assertBundleTree(b, { + name: 'index.js', + assets: ['index.js', 'composes-1.css', 'composes-2.css', 'mixins.css'], + childBundles: [ + { + name: 'index.css', + assets: ['composes-1.css', 'composes-2.css', 'mixins.css'], + childBundles: [ + { + type: 'map' + } + ] + }, + { + type: 'map' + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'function'); + + let value = output(); + const composes1Classes = value.composes1.split(' '); + const composes2Classes = value.composes2.split(' '); + assert(composes1Classes[0].startsWith('_composes1_')); + assert(composes1Classes[1].startsWith('_test_')); + assert(composes2Classes[0].startsWith('_composes2_')); + assert(composes2Classes[1].startsWith('_test_')); + + let css = await fs.readFile( + path.join(__dirname, '/dist/index.css'), + 'utf8' + ); + let cssClass1 = value.composes1.match(/(_composes1_[0-9a-z]+)/)[1]; + assert(css.includes(`.${cssClass1}`)); + let cssClass2 = value.composes2.match(/(_composes2_[0-9a-z]+)/)[1]; + assert(css.includes(`.${cssClass2}`)); + }); + + it('should not include css twice for postcss composes imports', async function() { + let b = await bundle( + path.join(__dirname, '/integration/postcss-composes/index.js') + ); + + await run(b); + + let css = await fs.readFile( + path.join(__dirname, '/dist/index.css'), + 'utf8' + ); + assert.equal( + css.indexOf('height: 100px;'), + css.lastIndexOf('height: 100px;') + ); + }); + + it('should support postcss composes imports for sass', async function() { + let b = await bundle( + path.join(__dirname, '/integration/postcss-composes/index2.js') + ); + + await assertBundleTree(b, { + name: 'index2.js', + assets: ['index2.js', 'composes-3.css', 'mixins.scss'], + childBundles: [ + { + name: 'index2.css', + assets: ['composes-3.css', 'mixins.scss'], + childBundles: [ + { + type: 'map' + } + ] + }, + { + type: 'map' + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'function'); + + let value = output(); + const composes3Classes = value.composes3.split(' '); + assert(composes3Classes[0].startsWith('_composes3_')); + assert(composes3Classes[1].startsWith('_test_')); + + let css = await fs.readFile( + path.join(__dirname, '/dist/index2.css'), + 'utf8' + ); + assert(css.includes('height: 200px;')); + }); + + it('should support postcss composes imports with custom path names', async function() { + let b = await bundle( + path.join(__dirname, '/integration/postcss-composes/index3.js') + ); + + await assertBundleTree(b, { + name: 'index3.js', + assets: ['index3.js', 'composes-4.css', 'mixins.css'], + childBundles: [ + { + name: 'index3.css', + assets: ['composes-4.css', 'mixins.css'], + childBundles: [ + { + type: 'map' + } + ] + }, + { + type: 'map' + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'function'); + + let value = output(); + const composes4Classes = value.composes4.split(' '); + assert(composes4Classes[0].startsWith('_composes4_')); + assert(composes4Classes[1].startsWith('_test_')); + + let css = await fs.readFile( + path.join(__dirname, '/dist/index3.css'), + 'utf8' + ); + assert(css.includes('height: 100px;')); + }); + + it('should support deep nested postcss composes imports', async function() { + let b = await bundle( + path.join(__dirname, '/integration/postcss-composes/index4.js') + ); + + await assertBundleTree(b, { + name: 'index4.js', + assets: [ + 'index4.js', + 'composes-5.css', + 'mixins-intermediate.css', + 'mixins.css' + ], + childBundles: [ + { + name: 'index4.css', + assets: ['composes-5.css', 'mixins-intermediate.css', 'mixins.css'], + childBundles: [ + { + type: 'map' + } + ] + }, + { + type: 'map' + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'function'); + + let value = output(); + const composes5Classes = value.composes5.split(' '); + assert(composes5Classes[0].startsWith('_composes5_')); + assert(composes5Classes[1].startsWith('_intermediate_')); + assert(composes5Classes[2].startsWith('_test_')); + + let css = await fs.readFile( + path.join(__dirname, '/dist/index4.css'), + 'utf8' + ); + assert(css.includes('height: 100px;')); + assert(css.includes('height: 300px;')); + assert(css.indexOf('._test_') < css.indexOf('._intermediate_')); + }); + + it('should support postcss composes imports for multiple selectors', async function() { + let b = await bundle( + path.join(__dirname, '/integration/postcss-composes/index5.js') + ); + + await assertBundleTree(b, { + name: 'index5.js', + assets: ['index5.js', 'composes-6.css', 'mixins.css'], + childBundles: [ + { + name: 'index5.css', + assets: ['composes-6.css', 'mixins.css'], + childBundles: [ + { + type: 'map' + } + ] + }, + { + type: 'map' + } + ] + }); + + let output = await run(b); + assert.equal(typeof output, 'function'); + + let value = output(); + const composes6Classes = value.composes6.split(' '); + assert(composes6Classes[0].startsWith('_composes6_')); + assert(composes6Classes[1].startsWith('_test_')); + assert(composes6Classes[2].startsWith('_test-2_')); + }); + it('should minify CSS in production mode', async function() { let b = await bundle( path.join(__dirname, '/integration/cssnano/index.js'), @@ -277,7 +581,7 @@ describe('css', function() { ); assert(css.includes('.local')); assert(css.includes('.index')); - assert(!css.includes('\n')); + assert.equal(css.split('\n').length, 2); // sourceMappingURL }); it('should automatically install postcss plugins with npm if needed', async function() { diff --git a/packages/core/integration-tests/test/elm.js b/packages/core/integration-tests/test/elm.js index 4ab71a716db..972c5a3a4a0 100644 --- a/packages/core/integration-tests/test/elm.js +++ b/packages/core/integration-tests/test/elm.js @@ -1,6 +1,6 @@ const assert = require('assert'); const fs = require('@parcel/fs'); -const {bundle, assertBundleTree, run} = require('./utils'); +const {bundle, assertBundleTree, run} = require('@parcel/test-utils'); describe('elm', function() { it('should produce a basic Elm bundle', async function() { diff --git a/packages/core/integration-tests/test/encodedURI.js b/packages/core/integration-tests/test/encodedURI.js index 8450f167962..4aecc42eff2 100644 --- a/packages/core/integration-tests/test/encodedURI.js +++ b/packages/core/integration-tests/test/encodedURI.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, assertBundleTree} = require('./utils'); +const {bundle, assertBundleTree} = require('@parcel/test-utils'); describe('encodedURI', function() { it('should support bundling files which names in encoded URI', async function() { diff --git a/packages/core/integration-tests/test/fs.js b/packages/core/integration-tests/test/fs.js index 1bfa96c2721..f466d80218f 100644 --- a/packages/core/integration-tests/test/fs.js +++ b/packages/core/integration-tests/test/fs.js @@ -1,7 +1,7 @@ const assert = require('assert'); const fs = require('@parcel/fs'); const path = require('path'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('fs', function() { describe('--target=browser', function() { diff --git a/packages/core/integration-tests/test/glob.js b/packages/core/integration-tests/test/glob.js index 9663d7be4b6..96b4582bc29 100644 --- a/packages/core/integration-tests/test/glob.js +++ b/packages/core/integration-tests/test/glob.js @@ -1,7 +1,7 @@ const assert = require('assert'); const fs = require('@parcel/fs'); const path = require('path'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('glob', function() { it('should require a glob of files', async function() { @@ -54,7 +54,11 @@ describe('glob', function() { { name: 'index.css', assets: ['index.css', 'other.css', 'local.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' diff --git a/packages/core/integration-tests/test/glsl.js b/packages/core/integration-tests/test/glsl.js index b8b1db9e3db..e16f033f638 100644 --- a/packages/core/integration-tests/test/glsl.js +++ b/packages/core/integration-tests/test/glsl.js @@ -1,7 +1,12 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, run, assertBundleTree, normaliseNewlines} = require('./utils'); +const { + bundle, + run, + assertBundleTree, + normaliseNewlines +} = require('@parcel/test-utils'); describe('glsl', function() { it('should support requiring GLSL files via glslify', async function() { diff --git a/packages/core/integration-tests/test/graphql.js b/packages/core/integration-tests/test/graphql.js index 4c890f7b1dc..2a90d4b22ba 100644 --- a/packages/core/integration-tests/test/graphql.js +++ b/packages/core/integration-tests/test/graphql.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const gql = require('graphql-tag'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('graphql', function() { it('should support requiring graphql files', async function() { diff --git a/packages/core/integration-tests/test/hmr.js b/packages/core/integration-tests/test/hmr.js index e06276c6e30..0fe734ce817 100644 --- a/packages/core/integration-tests/test/hmr.js +++ b/packages/core/integration-tests/test/hmr.js @@ -1,7 +1,7 @@ const assert = require('assert'); const fs = require('@parcel/fs'); const path = require('path'); -const {bundler, run, rimraf, ncp} = require('./utils'); +const {bundler, run, rimraf, ncp} = require('@parcel/test-utils'); const {sleep} = require('@parcel/test-utils'); const WebSocket = require('ws'); const json5 = require('json5'); @@ -287,6 +287,37 @@ describe('hmr', function() { assert.deepEqual(outputs, [3, 10]); }); + it('should work with circular dependencies', async function() { + await ncp( + path.join(__dirname, '/integration/hmr-circular'), + path.join(__dirname, '/input') + ); + + b = bundler(path.join(__dirname, '/input/index.js'), { + watch: true, + hmr: true + }); + let bundle = await b.bundle(); + let outputs = []; + + await run(bundle, { + output(o) { + outputs.push(o); + } + }); + + assert.deepEqual(outputs, [3]); + + await sleep(100); + fs.writeFile( + path.join(__dirname, '/input/local.js'), + "var other = require('./index.js'); exports.a = 5; exports.b = 5;" + ); + + await nextEvent(b, 'bundled'); + assert.deepEqual(outputs, [3, 10]); + }); + it('should call dispose and accept callbacks', async function() { await ncp( path.join(__dirname, '/integration/hmr-callbacks'), @@ -361,6 +392,45 @@ describe('hmr', function() { assert.deepEqual(outputs, [3, 10]); }); + it('should bubble up HMR events to a page reload', async function() { + await ncp( + path.join(__dirname, '/integration/hmr-reload'), + path.join(__dirname, '/input') + ); + + b = bundler(path.join(__dirname, '/input/index.js'), { + watch: true, + hmr: true + }); + let bundle = await b.bundle(); + + let outputs = []; + let ctx = await run( + bundle, + { + output(o) { + outputs.push(o); + } + }, + {require: false} + ); + let spy = sinon.spy(ctx.location, 'reload'); + + await sleep(50); + assert.deepEqual(outputs, [3]); + assert(spy.notCalled); + + await sleep(100); + fs.writeFile( + path.join(__dirname, '/input/local.js'), + 'exports.a = 5; exports.b = 5;' + ); + + await nextEvent(b, 'bundled'); + assert.deepEqual(outputs, [3]); + assert(spy.calledOnce); + }); + it('should log emitted errors and show an error overlay', async function() { await ncp( path.join(__dirname, '/integration/commonjs'), @@ -529,4 +599,56 @@ describe('hmr', function() { await buildEnd; }); + + it('should watch new dependencies that cause errors', async function() { + await ncp( + path.join(__dirname, '/integration/elm-dep-error'), + path.join(__dirname, '/input') + ); + + b = bundler(path.join(__dirname, '/input/index.js'), { + watch: true, + hmr: true + }); + await b.bundle(); + + ws = new WebSocket('ws://localhost:' + b.options.hmrPort); + + const buildEnd = nextEvent(b, 'buildEnd'); + + await sleep(100); + fs.writeFile( + path.join(__dirname, '/input/src/Main.elm'), + ` +module Main exposing (main) + +import BrokenDep +import Html + +main = + Html.text "Hello, world!" + ` + ); + + let msg = JSON.parse(await nextEvent(ws, 'message')); + assert.equal(msg.type, 'error'); + + await sleep(100); + fs.writeFile( + path.join(__dirname, '/input/src/BrokenDep.elm'), + ` +module BrokenDep exposing (anError) + + +anError : String +anError = + "fixed" + ` + ); + + msg = JSON.parse(await nextEvent(ws, 'message')); + assert.equal(msg.type, 'error-resolved'); + + await buildEnd; + }); }); diff --git a/packages/core/integration-tests/test/html.js b/packages/core/integration-tests/test/html.js index ed3962951e4..30921296ef8 100644 --- a/packages/core/integration-tests/test/html.js +++ b/packages/core/integration-tests/test/html.js @@ -1,6 +1,6 @@ const assert = require('assert'); const fs = require('@parcel/fs'); -const {bundle, assertBundleTree} = require('./utils'); +const {bundle, assertBundleTree} = require('@parcel/test-utils'); const path = require('path'); describe('html', function() { @@ -24,7 +24,11 @@ describe('html', function() { { type: 'css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'html', @@ -151,7 +155,11 @@ describe('html', function() { { type: 'css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -185,7 +193,11 @@ describe('html', function() { { type: 'css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -219,9 +231,6 @@ describe('html', function() { type: 'css', assets: ['index.css'], childBundles: [ - { - type: 'map' - }, { type: 'js', assets: [ @@ -230,7 +239,14 @@ describe('html', function() { 'css-loader.js', 'hmr-runtime.js' ], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] + }, + { + type: 'map' } ] } @@ -257,7 +273,11 @@ describe('html', function() { { type: 'css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'map' @@ -280,16 +300,32 @@ describe('html', function() { }); it('should minify HTML in production mode', async function() { - await bundle(path.join(__dirname, '/integration/htmlnano/index.html'), { + let inputFile = path.join(__dirname, '/integration/htmlnano/index.html'); + await bundle(inputFile, { production: true }); - let html = await fs.readFile( - path.join(__dirname, '/dist/index.html'), - 'utf8' - ); + let inputSize = (await fs.stat(inputFile)).size; + + let outputFile = path.join(__dirname, '/dist/index.html'); + let outputSize = (await fs.stat(outputFile)).size; + + assert(inputSize > outputSize); + + let html = await fs.readFile(outputFile, 'utf8'); assert(html.includes('Other page')); - assert(!html.includes('\n')); + }); + + it('should work with an empty html file', async function() { + let inputFile = path.join(__dirname, '/integration/html-empty/index.html'); + await bundle(inputFile, { + minify: false + }); + + let outputFile = path.join(__dirname, '/dist/index.html'); + + let html = await fs.readFile(outputFile, 'utf8'); + assert.equal(html.length, 0); }); it('should read .htmlnanorc and minify HTML in production mode', async function() { @@ -326,19 +362,23 @@ describe('html', function() { }); it('should not minify default values inside HTML in production mode', async function() { - await bundle( - path.join(__dirname, '/integration/htmlnano-defaults-form/index.html'), - { - production: true - } + let inputFile = path.join( + __dirname, + '/integration/htmlnano-defaults-form/index.html' ); + await bundle(inputFile, { + production: true + }); - let html = await fs.readFile( - path.join(__dirname, '/dist/index.html'), - 'utf8' - ); + let inputSize = (await fs.stat(inputFile)).size; + + let outputFile = path.join(__dirname, '/dist/index.html'); + let outputSize = (await fs.stat(outputFile)).size; + + assert(inputSize > outputSize); + + let html = await fs.readFile(outputFile, 'utf8'); assert(html.includes('')); - assert(!html.includes('\n')); }); it('should not prepend the public path to assets with remote URLs', async function() { diff --git a/packages/core/integration-tests/test/integration/css-url-relative/src/a/style1.css b/packages/core/integration-tests/test/integration/css-url-relative/src/a/style1.css new file mode 100644 index 00000000000..8ad6f805d0d --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-url-relative/src/a/style1.css @@ -0,0 +1,3 @@ +body { + background-image: url('../foo.png'); +} diff --git a/packages/core/integration-tests/test/integration/css-url-relative/src/b/style2.css b/packages/core/integration-tests/test/integration/css-url-relative/src/b/style2.css new file mode 100644 index 00000000000..b05faf8ead0 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-url-relative/src/b/style2.css @@ -0,0 +1,3 @@ +body { + color: red; +} diff --git a/packages/core/integration-tests/test/integration/css-url-relative/src/foo.png b/packages/core/integration-tests/test/integration/css-url-relative/src/foo.png new file mode 100644 index 00000000000..8a1daa0121d Binary files /dev/null and b/packages/core/integration-tests/test/integration/css-url-relative/src/foo.png differ diff --git a/packages/core/integration-tests/test/integration/elm-dep-error/elm.json b/packages/core/integration-tests/test/integration/elm-dep-error/elm.json new file mode 100644 index 00000000000..dd41cae2a31 --- /dev/null +++ b/packages/core/integration-tests/test/integration/elm-dep-error/elm.json @@ -0,0 +1,24 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.0", + "elm/core": "1.0.0", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.0.0", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/elm-dep-error/index.js b/packages/core/integration-tests/test/integration/elm-dep-error/index.js new file mode 100644 index 00000000000..61906ed5a66 --- /dev/null +++ b/packages/core/integration-tests/test/integration/elm-dep-error/index.js @@ -0,0 +1,5 @@ +var local = require('./src/Main.elm'); + +module.exports = function () { + return local; +}; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/elm-dep-error/src/BrokenDep.elm b/packages/core/integration-tests/test/integration/elm-dep-error/src/BrokenDep.elm new file mode 100644 index 00000000000..b52d49738bd --- /dev/null +++ b/packages/core/integration-tests/test/integration/elm-dep-error/src/BrokenDep.elm @@ -0,0 +1,8 @@ +module BrokenDep exposing (anError) + +{- This module causes a compiler error -} + + +anError : String +anError = + 2 diff --git a/packages/core/integration-tests/test/integration/elm-dep-error/src/Main.elm b/packages/core/integration-tests/test/integration/elm-dep-error/src/Main.elm new file mode 100644 index 00000000000..67393aaa654 --- /dev/null +++ b/packages/core/integration-tests/test/integration/elm-dep-error/src/Main.elm @@ -0,0 +1,7 @@ +module Main exposing (main) + +import Html + + +main = + Html.text "Hello, world!" diff --git a/packages/core/integration-tests/test/integration/globals/index.js b/packages/core/integration-tests/test/integration/globals/index.js index fa6f5ff1411..d036bcb5735 100644 --- a/packages/core/integration-tests/test/integration/globals/index.js +++ b/packages/core/integration-tests/test/integration/globals/index.js @@ -2,7 +2,7 @@ module.exports = function () { return { dir: __dirname, file: __filename, - buf: new Buffer(process.title).toString('base64'), + buf: Buffer.from(process.title).toString('base64'), global: !!global.document }; }; diff --git a/packages/core/integration-tests/test/integration/hmr-circular/index.js b/packages/core/integration-tests/test/integration/hmr-circular/index.js new file mode 100644 index 00000000000..d46510716d1 --- /dev/null +++ b/packages/core/integration-tests/test/integration/hmr-circular/index.js @@ -0,0 +1,11 @@ +var local = require('./local'); + +function run() { + output(local.a + local.b); +} + +module.hot.accept(); + +run(); + +module.exports = 'value'; diff --git a/packages/core/integration-tests/test/integration/hmr-circular/local.js b/packages/core/integration-tests/test/integration/hmr-circular/local.js new file mode 100644 index 00000000000..3bb6f4669b5 --- /dev/null +++ b/packages/core/integration-tests/test/integration/hmr-circular/local.js @@ -0,0 +1,4 @@ +var other = require('./index.js'); + +exports.a = 1; +exports.b = 2; diff --git a/packages/core/integration-tests/test/integration/hmr-dynamic/index.js b/packages/core/integration-tests/test/integration/hmr-dynamic/index.js index 37ed6fb597c..ee3c1d451fb 100644 --- a/packages/core/integration-tests/test/integration/hmr-dynamic/index.js +++ b/packages/core/integration-tests/test/integration/hmr-dynamic/index.js @@ -6,4 +6,6 @@ function run() { }); }; +module.hot.accept(); + run(); diff --git a/packages/core/integration-tests/test/integration/hmr-reload/index.js b/packages/core/integration-tests/test/integration/hmr-reload/index.js new file mode 100644 index 00000000000..bd54fc09f6e --- /dev/null +++ b/packages/core/integration-tests/test/integration/hmr-reload/index.js @@ -0,0 +1,7 @@ +var local = require('./local'); + +function run() { + output(local.a + local.b); +} + +run(); diff --git a/packages/core/integration-tests/test/integration/hmr-reload/local.js b/packages/core/integration-tests/test/integration/hmr-reload/local.js new file mode 100644 index 00000000000..59aa6ffd125 --- /dev/null +++ b/packages/core/integration-tests/test/integration/hmr-reload/local.js @@ -0,0 +1,2 @@ +exports.a = 1; +exports.b = 2; diff --git a/packages/core/integration-tests/test/integration/hmr/index.js b/packages/core/integration-tests/test/integration/hmr/index.js index bd54fc09f6e..21178bc0101 100644 --- a/packages/core/integration-tests/test/integration/hmr/index.js +++ b/packages/core/integration-tests/test/integration/hmr/index.js @@ -4,4 +4,6 @@ function run() { output(local.a + local.b); } +module.hot.accept(); + run(); diff --git a/packages/core/integration-tests/test/integration/html-empty/index.html b/packages/core/integration-tests/test/integration/html-empty/index.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/jsx-hyperapp-no-dep/index.js b/packages/core/integration-tests/test/integration/jsx-hyperapp-no-dep/index.js new file mode 100644 index 00000000000..4a97bbe9739 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-hyperapp-no-dep/index.js @@ -0,0 +1,3 @@ +import * as Hyperapp from 'hyperapp' + +module.exports =
; diff --git a/packages/core/integration-tests/test/integration/jsx-hyperapp-no-dep/package.json b/packages/core/integration-tests/test/integration/jsx-hyperapp-no-dep/package.json new file mode 100644 index 00000000000..352055cdf83 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-hyperapp-no-dep/package.json @@ -0,0 +1,3 @@ +{ + "private": true +} diff --git a/packages/core/integration-tests/test/integration/jsx-nervjs-no-dep/index.js b/packages/core/integration-tests/test/integration/jsx-nervjs-no-dep/index.js new file mode 100644 index 00000000000..9c8f2c81f29 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-nervjs-no-dep/index.js @@ -0,0 +1,3 @@ +import * as Nerv from 'nervjs'; + +module.exports =
; diff --git a/packages/core/integration-tests/test/integration/jsx-nervjs-no-dep/package.json b/packages/core/integration-tests/test/integration/jsx-nervjs-no-dep/package.json new file mode 100644 index 00000000000..352055cdf83 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-nervjs-no-dep/package.json @@ -0,0 +1,3 @@ +{ + "private": true +} diff --git a/packages/core/integration-tests/test/integration/jsx-preact-no-dep/index.js b/packages/core/integration-tests/test/integration/jsx-preact-no-dep/index.js new file mode 100644 index 00000000000..9483f2ef3e4 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-preact-no-dep/index.js @@ -0,0 +1,3 @@ +const Preact = require('preact'); + +module.exports =
; diff --git a/packages/core/integration-tests/test/integration/jsx-preact-no-dep/package.json b/packages/core/integration-tests/test/integration/jsx-preact-no-dep/package.json new file mode 100644 index 00000000000..352055cdf83 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-preact-no-dep/package.json @@ -0,0 +1,3 @@ +{ + "private": true +} diff --git a/packages/core/integration-tests/test/integration/jsx-react-no-dep/index.js b/packages/core/integration-tests/test/integration/jsx-react-no-dep/index.js new file mode 100644 index 00000000000..4f5a71615ed --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-react-no-dep/index.js @@ -0,0 +1,3 @@ +import * as React from 'react'; + +module.exports =
; diff --git a/packages/core/integration-tests/test/integration/jsx-react-no-dep/package.json b/packages/core/integration-tests/test/integration/jsx-react-no-dep/package.json new file mode 100644 index 00000000000..352055cdf83 --- /dev/null +++ b/packages/core/integration-tests/test/integration/jsx-react-no-dep/package.json @@ -0,0 +1,3 @@ +{ + "private": true +} diff --git a/packages/core/integration-tests/test/integration/markdown/100x100.png b/packages/core/integration-tests/test/integration/markdown/100x100.png new file mode 100644 index 00000000000..8a1daa0121d Binary files /dev/null and b/packages/core/integration-tests/test/integration/markdown/100x100.png differ diff --git a/packages/core/integration-tests/test/integration/markdown/index.md b/packages/core/integration-tests/test/integration/markdown/index.md new file mode 100644 index 00000000000..d1d6628f15c --- /dev/null +++ b/packages/core/integration-tests/test/integration/markdown/index.md @@ -0,0 +1,5 @@ +# heading1 + +content content content + +[image](./100x100.png) diff --git a/packages/core/integration-tests/test/integration/postcss-composes/.postcssrc b/packages/core/integration-tests/test/integration/postcss-composes/.postcssrc new file mode 100644 index 00000000000..050061e1080 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/.postcssrc @@ -0,0 +1,3 @@ +{ + "modules": true +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/composes-1.css b/packages/core/integration-tests/test/integration/postcss-composes/composes-1.css new file mode 100644 index 00000000000..547498770fc --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/composes-1.css @@ -0,0 +1,4 @@ +.composes1 { + composes: test from './mixins.css'; + border: 3px solid orange; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/composes-2.css b/packages/core/integration-tests/test/integration/postcss-composes/composes-2.css new file mode 100644 index 00000000000..98163adf124 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/composes-2.css @@ -0,0 +1,4 @@ +.composes2 { + composes: test from './mixins.css'; + border: 3px solid red; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/composes-3.css b/packages/core/integration-tests/test/integration/postcss-composes/composes-3.css new file mode 100644 index 00000000000..862fe412920 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/composes-3.css @@ -0,0 +1,4 @@ +.composes3 { + composes: test from './mixins.scss'; + border: 3px solid brown; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/composes-4.css b/packages/core/integration-tests/test/integration/postcss-composes/composes-4.css new file mode 100644 index 00000000000..b0f3992251d --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/composes-4.css @@ -0,0 +1,4 @@ +.composes4 { + composes: test from '~mixins.css'; + border: 3px solid black; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/composes-5.css b/packages/core/integration-tests/test/integration/postcss-composes/composes-5.css new file mode 100644 index 00000000000..4522e2bfdff --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/composes-5.css @@ -0,0 +1,4 @@ +.composes5 { + composes: intermediate from './mixins-intermediate.css'; + border: 3px solid yellow; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/composes-6.css b/packages/core/integration-tests/test/integration/postcss-composes/composes-6.css new file mode 100644 index 00000000000..780d4fe80b0 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/composes-6.css @@ -0,0 +1,4 @@ +.composes6 { + composes: test test-2 from './mixins.css'; + border: 3px solid orangered; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/index.js b/packages/core/integration-tests/test/integration/postcss-composes/index.js new file mode 100644 index 00000000000..19a9be12e73 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/index.js @@ -0,0 +1,6 @@ +var map1 = require('./composes-1.css'); +var map2 = require('./composes-2.css'); + +module.exports = function () { + return Object.assign({}, map1, map2); +}; diff --git a/packages/core/integration-tests/test/integration/postcss-composes/index2.js b/packages/core/integration-tests/test/integration/postcss-composes/index2.js new file mode 100644 index 00000000000..b72aba8c409 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/index2.js @@ -0,0 +1,5 @@ +var map3 = require('./composes-3.css'); + +module.exports = function () { + return map3; +}; diff --git a/packages/core/integration-tests/test/integration/postcss-composes/index3.js b/packages/core/integration-tests/test/integration/postcss-composes/index3.js new file mode 100644 index 00000000000..e1eb241f911 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/index3.js @@ -0,0 +1,5 @@ +var map4 = require('./composes-4.css'); + +module.exports = function () { + return map4; +}; diff --git a/packages/core/integration-tests/test/integration/postcss-composes/index4.js b/packages/core/integration-tests/test/integration/postcss-composes/index4.js new file mode 100644 index 00000000000..315561cbd3b --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/index4.js @@ -0,0 +1,5 @@ +var map5 = require('./composes-5.css'); + +module.exports = function () { + return map5; +}; diff --git a/packages/core/integration-tests/test/integration/postcss-composes/index5.js b/packages/core/integration-tests/test/integration/postcss-composes/index5.js new file mode 100644 index 00000000000..31932b14b88 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/index5.js @@ -0,0 +1,5 @@ +var map6 = require('./composes-6.css'); + +module.exports = function () { + return map6; +}; diff --git a/packages/core/integration-tests/test/integration/postcss-composes/mixins-intermediate.css b/packages/core/integration-tests/test/integration/postcss-composes/mixins-intermediate.css new file mode 100644 index 00000000000..ebbbcafc863 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/mixins-intermediate.css @@ -0,0 +1,4 @@ +.intermediate { + composes: test from './mixins.css'; + height: 300px; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/mixins.css b/packages/core/integration-tests/test/integration/postcss-composes/mixins.css new file mode 100644 index 00000000000..2de4a887869 --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/mixins.css @@ -0,0 +1,8 @@ +.test { + height: 100px; + width: 100px; +} + +.test-2 { + background: red; +} diff --git a/packages/core/integration-tests/test/integration/postcss-composes/mixins.scss b/packages/core/integration-tests/test/integration/postcss-composes/mixins.scss new file mode 100644 index 00000000000..6a801aa790a --- /dev/null +++ b/packages/core/integration-tests/test/integration/postcss-composes/mixins.scss @@ -0,0 +1,6 @@ +$test: 200px; + +.test { + height: $test; + width: $test; +} diff --git a/packages/core/integration-tests/test/integration/process/index.js b/packages/core/integration-tests/test/integration/process/index.js new file mode 100644 index 00000000000..469e248a1e5 --- /dev/null +++ b/packages/core/integration-tests/test/integration/process/index.js @@ -0,0 +1,8 @@ +process.browser = false +module.exports = function () { + return process.browser && test(process.browser); +}; + +function test(val) { + return val; +} diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/a.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/a.js new file mode 100644 index 00000000000..0888e345bef --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/a.js @@ -0,0 +1 @@ +export default import("./b.js").then(b => b.default); diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/b.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/b.js new file mode 100644 index 00000000000..30628ea0fbe --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/b.js @@ -0,0 +1 @@ +export default import('./c.js').then(b => b.default + 1); diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/c.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/c.js new file mode 100644 index 00000000000..67d639a53f2 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/dynamic-import-dynamic/c.js @@ -0,0 +1 @@ +export default 122; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/export-undefined/a.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/export-undefined/a.js new file mode 100644 index 00000000000..eb53777b1b3 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/export-undefined/a.js @@ -0,0 +1 @@ +export {Test}; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/a.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/a.js new file mode 100644 index 00000000000..1077b636aca --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/a.js @@ -0,0 +1,3 @@ +import { x } from './b.js'; + +output = x; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/b.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/b.js new file mode 100644 index 00000000000..154372df193 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/b.js @@ -0,0 +1,3 @@ +export const x = 'foobar'; + +export { x as y } from './c.js' diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/c.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/c.js new file mode 100644 index 00000000000..0190dbd267f --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-renamed/c.js @@ -0,0 +1 @@ +export const x = 'xyz'; diff --git a/packages/core/integration-tests/test/integration/scss-absolute-imports/b.scss b/packages/core/integration-tests/test/integration/scss-absolute-imports/b.scss new file mode 100644 index 00000000000..b28d1ab0817 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scss-absolute-imports/b.scss @@ -0,0 +1,3 @@ +.b { + color: red; +} diff --git a/packages/core/integration-tests/test/integration/scss-absolute-imports/style.scss b/packages/core/integration-tests/test/integration/scss-absolute-imports/style.scss new file mode 100644 index 00000000000..f7caaac8c3b --- /dev/null +++ b/packages/core/integration-tests/test/integration/scss-absolute-imports/style.scss @@ -0,0 +1,5 @@ +@import '/b.scss'; + +.a { + color: blue; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-existing/style.css b/packages/core/integration-tests/test/integration/sourcemap-css-existing/style.css new file mode 100644 index 00000000000..191ad2bf43d --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-existing/style.css @@ -0,0 +1,5 @@ +@import "./test/library.css"; + +main { + display: none; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.css b/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.css new file mode 100644 index 00000000000..b13111e24ed --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.css @@ -0,0 +1,2 @@ +body{font:100% Helvetica,sans-serif;color:#333}body div{background-color:red;width:100px;height:100px} +/*# sourceMappingURL=library.css.map*/ \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.css.map b/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.css.map new file mode 100644 index 00000000000..579f260dfcf --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["library.scss"],"names":[],"mappings":"AAGA,KACE,+BACA,WAEA,SACC,qBACA,YACA","file":"library.css.map","sourceRoot":".","sourcesContent":["$font-stack: Helvetica, sans-serif;\n$primary-color: #333;\n\nbody {\n font: 100% $font-stack;\n color: $primary-color;\n\n div {\n \tbackground-color: red;\n \twidth: 100px;\n \theight: 100px;\n }\n}"]} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.raw.scss b/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.raw.scss new file mode 100644 index 00000000000..93097df8f38 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-existing/test/library.raw.scss @@ -0,0 +1,13 @@ +$font-stack: Helvetica, sans-serif; +$primary-color: #333; + +body { + font: 100% $font-stack; + color: $primary-color; + + div { + background-color: red; + width: 100px; + height: 100px; + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-import/another-style.css b/packages/core/integration-tests/test/integration/sourcemap-css-import/another-style.css new file mode 100644 index 00000000000..107e9f29b84 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-import/another-style.css @@ -0,0 +1,3 @@ +main { + font-family: monospace; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-import/other-style.css b/packages/core/integration-tests/test/integration/sourcemap-css-import/other-style.css new file mode 100644 index 00000000000..6d844850414 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-import/other-style.css @@ -0,0 +1,5 @@ + +div { + width: 100px; + height: 100px; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-css-import/style.css b/packages/core/integration-tests/test/integration/sourcemap-css-import/style.css new file mode 100644 index 00000000000..01ef4f07c71 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css-import/style.css @@ -0,0 +1,8 @@ + +@import "./other-style.css"; + +body { + background-color: red; +} + +@import "./another-style.css"; diff --git a/packages/core/integration-tests/test/integration/sourcemap-css/style.css b/packages/core/integration-tests/test/integration/sourcemap-css/style.css new file mode 100644 index 00000000000..0ac172751f5 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-css/style.css @@ -0,0 +1,4 @@ + +body { + background-color: red; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-less/style.less b/packages/core/integration-tests/test/integration/sourcemap-less/style.less new file mode 100644 index 00000000000..e1a4d641769 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-less/style.less @@ -0,0 +1,5 @@ +@value: 100px * 2; + +div { + width: @value; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-sass-imported/other.scss b/packages/core/integration-tests/test/integration/sourcemap-sass-imported/other.scss new file mode 100644 index 00000000000..3498c8f2370 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-sass-imported/other.scss @@ -0,0 +1,5 @@ +$variable: monospace; + +div { + font-family: $variable; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-sass-imported/style.css b/packages/core/integration-tests/test/integration/sourcemap-sass-imported/style.css new file mode 100644 index 00000000000..b486b67de91 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-sass-imported/style.css @@ -0,0 +1,5 @@ +@import "./other.scss"; + +body { + color: red; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-sass/style.scss b/packages/core/integration-tests/test/integration/sourcemap-sass/style.scss new file mode 100644 index 00000000000..8ce95c4110e --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-sass/style.scss @@ -0,0 +1,5 @@ +$variable: #333; + +body { + color: $variable; +} diff --git a/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl-multiple-entrypoints/a/index.js b/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl-multiple-entrypoints/a/index.js new file mode 100644 index 00000000000..d6357c10e58 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl-multiple-entrypoints/a/index.js @@ -0,0 +1 @@ +console.log('hello1'); \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl-multiple-entrypoints/b/index.js b/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl-multiple-entrypoints/b/index.js new file mode 100644 index 00000000000..a023d214778 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl-multiple-entrypoints/b/index.js @@ -0,0 +1 @@ +console.log('hello2'); \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl/index.js b/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl/index.js new file mode 100644 index 00000000000..ea17b22ee52 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sourcemap-sourcemappingurl/index.js @@ -0,0 +1 @@ +console.log('hello'); \ No newline at end of file diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js index a0474f8212e..a8b1fdd6c8a 100644 --- a/packages/core/integration-tests/test/javascript.js +++ b/packages/core/integration-tests/test/javascript.js @@ -8,8 +8,9 @@ const { assertBundleTree, deferred, ncp -} = require('./utils'); +} = require('@parcel/test-utils'); const {mkdirp} = require('@parcel/fs'); +const {symlinkPrivilegeWarning} = require('@parcel/test-utils'); const {symlinkSync} = require('fs'); describe('javascript', function() { @@ -802,7 +803,7 @@ describe('javascript', function() { assert.deepEqual(output(), { dir: path.join(__dirname, '/integration/globals'), file: path.join(__dirname, '/integration/globals/index.js'), - buf: new Buffer('browser').toString('base64'), + buf: Buffer.from('browser').toString('base64'), global: true }); }); @@ -855,6 +856,45 @@ describe('javascript', function() { assert.equal(output, 'bartest'); }); + it('should replace process.browser on --target=browser', async function() { + let b = await bundle( + path.join(__dirname, '/integration/process/index.js'), + { + target: 'browser' + } + ); + + let output = await run(b); + assert.ok(output.toString().indexOf('process.browser') === -1); + assert.equal(output(), true); + }); + + it('should not replace process.browser on --target=node', async function() { + let b = await bundle( + path.join(__dirname, '/integration/process/index.js'), + { + target: 'node' + } + ); + + let output = await run(b); + assert.ok(output.toString().indexOf('process.browser') !== -1); + assert.equal(output(), false); + }); + + it('should not replace process.browser on --target=electron', async function() { + let b = await bundle( + path.join(__dirname, '/integration/process/index.js'), + { + target: 'electron' + } + ); + + let output = await run(b); + assert.ok(output.toString().indexOf('process.browser') !== -1); + assert.equal(output(), false); + }); + it('should support adding implicit dependencies', async function() { let b = await bundle(path.join(__dirname, '/integration/json/index.js'), { delegate: { @@ -1290,21 +1330,28 @@ describe('javascript', function() { inputDir ); - // Create the symlink here to prevent cross platform and git issues - symlinkSync( - path.join(inputDir, 'packages/foo'), - path.join(inputDir, 'node_modules/foo'), - 'dir' - ); + try { + // Create the symlink here to prevent cross platform and git issues + symlinkSync( + path.join(inputDir, 'packages/foo'), + path.join(inputDir, 'node_modules/foo'), + 'dir' + ); - await bundle(inputDir + '/index.js'); + await bundle(inputDir + '/index.js'); - let file = await fs.readFile( - path.join(__dirname, '/dist/index.js'), - 'utf8' - ); - assert(file.includes('function Foo')); - assert(file.includes('function Bar')); + let file = await fs.readFile( + path.join(__dirname, '/dist/index.js'), + 'utf8' + ); + assert(file.includes('function Foo')); + assert(file.includes('function Bar')); + } catch (e) { + if (e.perm == 'EPERM') { + symlinkPrivilegeWarning(); + this.skip(); + } + } }); it('should not compile node_modules with a source field in package.json when not symlinked', async function() { @@ -1343,6 +1390,28 @@ describe('javascript', function() { assert(file.includes('React.createElement("div"')); }); + it('should support compiling JSX in JS files with React dependency even if React is not specified as dependency', async function() { + let originalPkg = await fs.readFile( + __dirname + '/integration/jsx-react-no-dep/package.json' + ); + + await bundle( + path.join(__dirname, '/integration/jsx-react-no-dep/index.js') + ); + + let file = await fs.readFile( + path.join(__dirname, '/dist/index.js'), + 'utf8' + ); + + assert(file.includes('React.createElement("div"')); + + await fs.writeFile( + __dirname + '/integration/jsx-react-no-dep/package.json', + originalPkg + ); + }); + it('should support compiling JSX in JS files with Preact dependency', async function() { await bundle(path.join(__dirname, '/integration/jsx-preact/index.js')); @@ -1353,6 +1422,28 @@ describe('javascript', function() { assert(file.includes('h("div"')); }); + it('should support compiling JSX in JS files with Preact dependency even if Preact is not specified as dependency', async function() { + let originalPkg = await fs.readFile( + __dirname + '/integration/jsx-preact-no-dep/package.json' + ); + + await bundle( + path.join(__dirname, '/integration/jsx-preact-no-dep/index.js') + ); + + let file = await fs.readFile( + path.join(__dirname, '/dist/index.js'), + 'utf8' + ); + + assert(file.includes('h("div"')); + + await fs.writeFile( + __dirname + '/integration/jsx-preact-no-dep/package.json', + originalPkg + ); + }); + it('should support compiling JSX in JS files with Nerv dependency', async function() { await bundle(path.join(__dirname, '/integration/jsx-nervjs/index.js')); @@ -1363,6 +1454,28 @@ describe('javascript', function() { assert(file.includes('Nerv.createElement("div"')); }); + it('should support compiling JSX in JS files with Nerv dependency even if Nerv is not specified as dependency', async function() { + let originalPkg = await fs.readFile( + __dirname + '/integration/jsx-nervjs-no-dep/package.json' + ); + + await bundle( + path.join(__dirname, '/integration/jsx-nervjs-no-dep/index.js') + ); + + let file = await fs.readFile( + path.join(__dirname, '/dist/index.js'), + 'utf8' + ); + + assert(file.includes('Nerv.createElement("div"')); + + await fs.writeFile( + __dirname + '/integration/jsx-nervjs-no-dep/package.json', + originalPkg + ); + }); + it('should support compiling JSX in JS files with Hyperapp dependency', async function() { await bundle(path.join(__dirname, '/integration/jsx-hyperapp/index.js')); @@ -1370,7 +1483,30 @@ describe('javascript', function() { path.join(__dirname, '/dist/index.js'), 'utf8' ); + + assert(file.includes('h("div"')); + }); + + it('should support compiling JSX in JS files with Hyperapp dependency even if Hyperapp is not specified as dependency', async function() { + let originalPkg = await fs.readFile( + __dirname + '/integration/jsx-hyperapp-no-dep/package.json' + ); + + await bundle( + path.join(__dirname, '/integration/jsx-hyperapp-no-dep/index.js') + ); + + let file = await fs.readFile( + path.join(__dirname, '/dist/index.js'), + 'utf8' + ); + assert(file.includes('h("div"')); + + await fs.writeFile( + __dirname + '/integration/jsx-hyperapp-no-dep/package.json', + originalPkg + ); }); it('should support optional dependencies in try...catch blocks', async function() { diff --git a/packages/core/integration-tests/test/kotlin.js b/packages/core/integration-tests/test/kotlin.js index 0e62f88133d..43ddbcdc7a9 100644 --- a/packages/core/integration-tests/test/kotlin.js +++ b/packages/core/integration-tests/test/kotlin.js @@ -1,7 +1,16 @@ const assert = require('assert'); -const {bundle, assertBundleTree, run} = require('./utils'); +const {bundle, assertBundleTree, run} = require('@parcel/test-utils'); +const commandExists = require('command-exists'); describe('kotlin', function() { + if (!commandExists.sync('java')) { + // eslint-disable-next-line no-console + console.log( + 'Skipping Kotlin tests. Install https://www.java.com/download/ to run them.' + ); + return; + } + it('should produce a basic kotlin bundle', async function() { let b = await bundle(__dirname + '/integration/kotlin/index.js'); diff --git a/packages/core/integration-tests/test/less.js b/packages/core/integration-tests/test/less.js index da522dd66d0..2a700a0dd33 100644 --- a/packages/core/integration-tests/test/less.js +++ b/packages/core/integration-tests/test/less.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('less', function() { it('should support requiring less files', async function() { @@ -17,7 +17,11 @@ describe('less', function() { { name: 'index.css', assets: ['index.less'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -48,7 +52,11 @@ describe('less', function() { { name: 'index.css', assets: ['index.less'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -80,7 +88,11 @@ describe('less', function() { { name: 'index.css', assets: ['index.less'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -112,7 +124,11 @@ describe('less', function() { { name: 'index.css', assets: ['index.less'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -125,7 +141,11 @@ describe('less', function() { path.join(__dirname, '/dist/index.css'), 'utf8' ); - assert.equal(css, ''); + assert( + /^\/\*# sourceMappingURL=\/\w*\.css\.map \*\/$/.test( + css.replace('\n', '') + ) + ); }); it('should support linking to assets with url() from less', async function() { @@ -143,7 +163,11 @@ describe('less', function() { { name: 'index.css', assets: ['index.less'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'woff2', @@ -191,19 +215,23 @@ describe('less', function() { { name: 'index.css', assets: ['index.less'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); let output = await run(b); assert.equal(typeof output, 'function'); - assert.equal(output(), '_index_ku5n8_1'); + assert(output().startsWith('_index_')); let css = await fs.readFile( path.join(__dirname, '/dist/index.css'), 'utf8' ); - assert(css.includes('._index_ku5n8_1')); + assert(css.includes('._index_')); }); }); diff --git a/packages/core/integration-tests/test/markdown.js b/packages/core/integration-tests/test/markdown.js new file mode 100644 index 00000000000..017e8fe9d33 --- /dev/null +++ b/packages/core/integration-tests/test/markdown.js @@ -0,0 +1,33 @@ +const assert = require('assert'); +const path = require('path'); +const fs = require('@parcel/fs'); +const {bundle, run, assertBundleTree} = require('./utils'); + +describe('markdown', function() { + it('should support bundling Markdown', async function() { + let b = await bundle( + path.join(__dirname, '/integration/markdown/index.md') + ); + + await assertBundleTree(b, { + name: 'index.html', + assets: ['index.md'], + childBundles: [ + { + type: 'png', + assets: ['100x100.png'], + childBundles: [] + } + ] + }); + + let files = await fs.readdir(path.join(__dirname, '/dist')); + let html = await fs.readFile(path.join(__dirname, '/dist/index.html')); + for (let file of files) { + let ext = file.match(/\.([0-9a-z]+)(?:[?#]|$)/i)[0]; + if (file !== 'index.html' && ext !== '.map') { + assert(html.includes(file)); + } + } + }); +}); diff --git a/packages/core/integration-tests/test/parser.js b/packages/core/integration-tests/test/parser.js index af10583255a..8ff2d4e3418 100644 --- a/packages/core/integration-tests/test/parser.js +++ b/packages/core/integration-tests/test/parser.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, assertBundleTree} = require('./utils'); +const {bundle, assertBundleTree} = require('@parcel/test-utils'); describe('parser', function() { it('should support case-insensitive file extension', async function() { @@ -24,7 +24,11 @@ describe('parser', function() { { type: 'css', assets: ['index.cSs'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'html', diff --git a/packages/core/integration-tests/test/pug.js b/packages/core/integration-tests/test/pug.js index 79bc8f0f778..2962fdecac8 100644 --- a/packages/core/integration-tests/test/pug.js +++ b/packages/core/integration-tests/test/pug.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, assertBundleTree, normaliseNewlines} = require('./utils'); +const {bundle, assertBundleTree} = require('@parcel/test-utils'); describe('pug', function() { it('should support bundling HTML', async function() { @@ -24,7 +24,11 @@ describe('pug', function() { { type: 'css', assets: ['index.css'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'js', @@ -58,17 +62,13 @@ describe('pug', function() { assets: ['index.pug'] }); - const html = normaliseNewlines( - await fs.readFile(path.join(__dirname, '/dist/index.html'), 'utf-8') - ); - const expect = normaliseNewlines( - await fs.readFile( - path.join(__dirname, '/integration/pug-include-extends/expect.html'), - 'utf-8' - ) + const html = await fs.readFile( + path.join(__dirname, '/dist/index.html'), + 'utf-8' ); - assert.equal(html, expect, 'Content mismatch'); + assert(html.includes('')); + assert(html.includes("

Yep, it's working!

")); }); it('should support variables', async function() { diff --git a/packages/core/integration-tests/test/reason.js b/packages/core/integration-tests/test/reason.js index 6356858d7ea..bf81dade629 100644 --- a/packages/core/integration-tests/test/reason.js +++ b/packages/core/integration-tests/test/reason.js @@ -1,6 +1,6 @@ const assert = require('assert'); const path = require('path'); -const {bundle, run} = require('./utils'); +const {bundle, run} = require('@parcel/test-utils'); describe('reason', function() { it('should produce a bundle', async function() { diff --git a/packages/core/integration-tests/test/rust.js b/packages/core/integration-tests/test/rust.js index 90e4bd11b60..aa00fce6ed8 100644 --- a/packages/core/integration-tests/test/rust.js +++ b/packages/core/integration-tests/test/rust.js @@ -1,6 +1,6 @@ const assert = require('assert'); const path = require('path'); -const {bundle, bundler, run, assertBundleTree} = require('./utils'); +const {bundle, bundler, run, assertBundleTree} = require('@parcel/test-utils'); const fs = require('@parcel/fs'); const commandExists = require('command-exists'); diff --git a/packages/core/integration-tests/test/sass.js b/packages/core/integration-tests/test/sass.js index 5febcbd0e64..9640bd3a329 100644 --- a/packages/core/integration-tests/test/sass.js +++ b/packages/core/integration-tests/test/sass.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('sass', function() { it('should support requiring sass files', async function() { @@ -17,7 +17,11 @@ describe('sass', function() { { name: 'index.css', assets: ['index.sass'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -46,7 +50,11 @@ describe('sass', function() { { name: 'index.css', assets: ['index.scss'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -77,7 +85,11 @@ describe('sass', function() { { name: 'index.css', assets: ['index.scss'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -110,7 +122,11 @@ describe('sass', function() { { name: 'index.css', assets: ['index.scss'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -123,7 +139,11 @@ describe('sass', function() { path.join(__dirname, '/dist/index.css'), 'utf8' ); - assert.equal(css, ''); + assert( + /^\/\*# sourceMappingURL=\/\w*\.css\.map \*\/$/.test( + css.replace('\n', '') + ) + ); }); it('should support linking to assets with url() from scss', async function() { @@ -146,7 +166,11 @@ describe('sass', function() { { name: 'index.css', assets: ['index.scss'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'woff2', @@ -194,7 +218,11 @@ describe('sass', function() { { name: 'index.css', assets: ['index.scss'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -218,7 +246,12 @@ describe('sass', function() { await assertBundleTree(b, { name: 'index.css', - assets: ['index.sass'] + assets: ['index.sass'], + childBundles: [ + { + type: 'map' + } + ] }); let css = (await fs.readFile( @@ -228,4 +261,22 @@ describe('sass', function() { assert(css.includes('.foo { color: blue;')); assert(css.includes('.bar { color: green;')); }); + + it('should support absolute imports', async function() { + let b = await bundle( + path.join(__dirname, '/integration/scss-absolute-imports/style.scss') + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.scss'] + }); + + let css = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + assert(css.includes('.a')); + assert(css.includes('.b')); + }); }); diff --git a/packages/core/integration-tests/test/schema-jsonld.js b/packages/core/integration-tests/test/schema-jsonld.js index 5e7d24c1aec..24ead830864 100644 --- a/packages/core/integration-tests/test/schema-jsonld.js +++ b/packages/core/integration-tests/test/schema-jsonld.js @@ -1,4 +1,4 @@ -const {bundle, assertBundleTree} = require('./utils'); +const {bundle, assertBundleTree} = require('@parcel/test-utils'); describe('schema ld+json', function() { it('Should parse a LD+JSON schema and collect dependencies', async function() { diff --git a/packages/core/integration-tests/test/scope-hoisting.js b/packages/core/integration-tests/test/scope-hoisting.js index 139397dcf07..da271fece17 100644 --- a/packages/core/integration-tests/test/scope-hoisting.js +++ b/packages/core/integration-tests/test/scope-hoisting.js @@ -1,21 +1,12 @@ const assert = require('assert'); const path = require('path'); -const {bundle: _bundle, run} = require('./utils'); +const {bundle: _bundle, run} = require('@parcel/test-utils'); const fs = require('@parcel/fs'); const bundle = (name, opts = {}) => _bundle(name, Object.assign({scopeHoist: true}, opts)); describe('scope hoisting', function() { - if (process.platform === 'win32') { - // eslint-disable-next-line no-console - console.warn( - 'WARNING: Scope hoisting tests are disabled on windows due to ' + - 'filesystem errors. Feel free to look into this and contribute a fix!' - ); - return; - } - describe('es6', function() { it('supports default imports and exports of expressions', async function() { let b = await bundle( @@ -273,6 +264,18 @@ describe('scope hoisting', function() { assert.equal(await output.default, 5); }); + it('supports nested dynamic imports', async function() { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/dynamic-import-dynamic/a.js' + ) + ); + + let output = await run(b); + assert.equal(await output.default, 123); + }); + it('should not export function arguments', async function() { let b = await bundle( path.join( @@ -285,6 +288,23 @@ describe('scope hoisting', function() { assert.deepEqual(output, ['test']); }); + it('throws a meaningful error on undefined exports', async function() { + let threw = false; + try { + await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/export-undefined/a.js' + ) + ); + } catch (err) { + threw = true; + assert.equal(err.message, "export 'Test' is not defined"); + } + + assert(threw); + }); + it('supports import default CommonJS interop', async function() { let b = await bundle( path.join( @@ -321,6 +341,18 @@ describe('scope hoisting', function() { assert.deepEqual(output, 'foobar'); }); + it('supports requiring a re-exported and renamed ES6 import', async function() { + let b = await bundle( + path.join( + __dirname, + '/integration/scope-hoisting/es6/re-export-renamed/a.js' + ) + ); + + let output = await run(b); + assert.deepEqual(output, 'foobar'); + }); + it('keeps side effects by default', async function() { let b = await bundle( path.join( diff --git a/packages/core/integration-tests/test/server.js b/packages/core/integration-tests/test/server.js index 9dccf7ca8be..f5e18b07dab 100644 --- a/packages/core/integration-tests/test/server.js +++ b/packages/core/integration-tests/test/server.js @@ -2,7 +2,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); const logger = require('@parcel/logger'); -const {bundler} = require('./utils'); +const {bundler} = require('@parcel/test-utils'); const http = require('http'); const https = require('https'); const sinon = require('sinon'); diff --git a/packages/core/integration-tests/test/sourcemaps.js b/packages/core/integration-tests/test/sourcemaps.js index 4eafe6b6e05..1a412cf1904 100644 --- a/packages/core/integration-tests/test/sourcemaps.js +++ b/packages/core/integration-tests/test/sourcemaps.js @@ -1,8 +1,75 @@ const assert = require('assert'); const fs = require('@parcel/fs'); const path = require('path'); +const os = require('os'); const mapValidator = require('sourcemap-validator'); -const {bundler, bundle, run, assertBundleTree} = require('./utils'); +const SourceMap = + parseInt(process.versions.node, 10) < 8 + ? require('parcel-bundler/lib/SourceMap') + : require('parcel-bundler/src/SourceMap'); +const {bundler, bundle, run, assertBundleTree} = require('@parcel/test-utils'); + +function indexToLineCol(str, index) { + let beforeIndex = str.slice(0, index); + return { + line: beforeIndex.split('\n').length, + column: index - beforeIndex.lastIndexOf('\n') - 1 + }; +} + +function checkSourceMapping({ + map, + source, + generated, + str, + generatedStr = str, + sourcePath, + msg = '' +}) { + assert( + generated.indexOf(generatedStr) !== -1, + "'" + generatedStr + "' not in generated code" + ); + assert(source.indexOf(str) !== -1, "'" + str + "' not in source code"); + + let generatedPosition = indexToLineCol( + generated, + generated.indexOf(generatedStr) + ); + let sourcePosition = indexToLineCol(source, source.indexOf(str)); + + let index = map.findClosestGenerated( + generatedPosition.line, + generatedPosition.column + ); + + let mapping = map.mappings[index]; + assert(mapping, "no mapping for '" + str + "'" + msg); + + let generatedDiff = { + line: generatedPosition.line - mapping.generated.line, + column: generatedPosition.column - mapping.generated.column + }; + + let computedSourcePosition = { + line: mapping.original.line + generatedDiff.line, + column: mapping.original.column + generatedDiff.column + }; + + assert.deepStrictEqual( + { + line: computedSourcePosition.line, + column: computedSourcePosition.column, + source: mapping.source + }, + { + line: sourcePosition.line, + column: sourcePosition.column, + source: sourcePath + }, + "map '" + str + "'" + msg + ); +} describe('sourcemaps', function() { it('should create a valid sourcemap as a child of a JS bundle', async function() { @@ -14,18 +81,17 @@ describe('sourcemaps', function() { assets: ['index.js'], childBundles: [ { - name: 'index.map', + name: 'index.js.map', type: 'map' } ] }); - let raw = (await fs.readFile( - path.join(__dirname, '/dist/index.js') - )).toString(); - let map = (await fs.readFile( - path.join(__dirname, '/dist/index.map') - )).toString(); + let raw = await fs.readFile(path.join(__dirname, '/dist/index.js'), 'utf8'); + let map = await fs.readFile( + path.join(__dirname, '/dist/index.js.map'), + 'utf8' + ); mapValidator(raw, map); let mapObject = JSON.parse(map); assert( @@ -43,6 +109,7 @@ describe('sourcemaps', function() { ), 'combining sourceRoot and sources object should resolve to the original file' ); + assert.equal(mapObject.sources.length, 1); let output = await run(bu); assert.equal(typeof output, 'function'); @@ -59,18 +126,18 @@ describe('sourcemaps', function() { assets: ['index.ts'], childBundles: [ { - name: 'index.map', + name: 'index.js.map', type: 'map' } ] }); - let raw = (await fs.readFile( - path.join(__dirname, '/dist/index.js') - )).toString(); - let map = (await fs.readFile( - path.join(__dirname, '/dist/index.map') - )).toString(); + let raw = await fs.readFile(path.join(__dirname, '/dist/index.js'), 'utf8'); + let map = await fs.readFile( + path.join(__dirname, '/dist/index.js.map'), + 'utf8' + ); + assert.equal(JSON.parse(map).sources.length, 1); mapValidator(raw, map); let output = await run(b); @@ -88,18 +155,18 @@ describe('sourcemaps', function() { assets: ['index.ts', 'local.ts'], childBundles: [ { - name: 'index.map', + name: 'index.js.map', type: 'map' } ] }); - let raw = (await fs.readFile( - path.join(__dirname, '/dist/index.js') - )).toString(); - let map = (await fs.readFile( - path.join(__dirname, '/dist/index.map') - )).toString(); + let raw = await fs.readFile(path.join(__dirname, '/dist/index.js'), 'utf8'); + let map = await fs.readFile( + path.join(__dirname, '/dist/index.js.map'), + 'utf8' + ); + assert.equal(JSON.parse(map).sources.length, 2); mapValidator(raw, map); let output = await run(b); @@ -117,18 +184,17 @@ describe('sourcemaps', function() { assets: ['index.js', 'local.js', 'util.js'], childBundles: [ { - name: 'index.map', + name: 'index.js.map', type: 'map' } ] }); - let raw = (await fs.readFile( - path.join(__dirname, '/dist/index.js') - )).toString(); - let map = (await fs.readFile( - path.join(__dirname, '/dist/index.map') - )).toString(); + let raw = await fs.readFile(path.join(__dirname, '/dist/index.js'), 'utf8'); + let map = await fs.readFile( + path.join(__dirname, '/dist/index.js.map'), + 'utf8' + ); mapValidator(raw, map); let output = await run(b); @@ -149,18 +215,18 @@ describe('sourcemaps', function() { assets: ['index.js', 'local.js', 'util.js'], childBundles: [ { - name: 'index.map', + name: 'index.js.map', type: 'map' } ] }); - let raw = (await fs.readFile( - path.join(__dirname, '/dist/index.js') - )).toString(); - let map = (await fs.readFile( - path.join(__dirname, '/dist/index.map') - )).toString(); + let raw = await fs.readFile(path.join(__dirname, '/dist/index.js'), 'utf8'); + let map = await fs.readFile( + path.join(__dirname, '/dist/index.js.map'), + 'utf8' + ); + assert.equal(JSON.parse(map).sources.length, 3); mapValidator(raw, map); let output = await run(b); @@ -189,9 +255,10 @@ describe('sourcemaps', function() { ] }); - let jsOutput = (await fs.readFile( - Array.from(b.childBundles)[0].name - )).toString(); + let jsOutput = await fs.readFile( + Array.from(b.childBundles)[0].name, + 'utf8' + ); let sourcemapReference = path.join( __dirname, @@ -203,7 +270,8 @@ describe('sourcemaps', function() { 'referenced sourcemap should exist' ); - let map = (await fs.readFile(path.join(sourcemapReference))).toString(); + let map = await fs.readFile(path.join(sourcemapReference), 'utf8'); + assert.equal(JSON.parse(map).sources.length, 2); mapValidator(jsOutput, map); }); @@ -314,4 +382,526 @@ describe('sourcemaps', function() { ); mapValidator(jsOutput, map); }); + + it('should create correct sourceMappingURL', async function() { + const b = await bundle( + path.join(__dirname, '/integration/sourcemap-sourcemappingurl/index.js') + ); + + const jsOutput = await fs.readFile(b.name, 'utf8'); + assert(jsOutput.includes('//# sourceMappingURL=/index.js.map')); + }); + + it('should create correct sourceMappingURL with multiple entrypoints', async function() { + const b = await bundle([ + path.join( + __dirname, + '/integration/sourcemap-sourcemappingurl-multiple-entrypoints/a/index.js' + ), + path.join( + __dirname, + '/integration/sourcemap-sourcemappingurl-multiple-entrypoints/b/index.js' + ) + ]); + + const bundle1 = [...b.childBundles][0]; + const bundle2 = [...b.childBundles][1]; + const jsOutput1 = await fs.readFile(bundle1.name, 'utf8'); + const jsOutput2 = await fs.readFile(bundle2.name, 'utf8'); + + assert(jsOutput1.includes('//# sourceMappingURL=/a/index.js.map')); + assert(jsOutput2.includes('//# sourceMappingURL=/b/index.js.map')); + }); + + it('should create a valid sourcemap as a child of a CSS bundle', async function() { + async function test(minify) { + let b = await bundle( + path.join(__dirname, '/integration/sourcemap-css/style.css'), + {minify: true} + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.css'], + childBundles: [ + { + name: 'style.css.map', + type: 'map' + } + ] + }); + + let input = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-css/style.css'), + 'utf8' + ); + let raw = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + let map = JSON.parse( + await fs.readFile(path.join(__dirname, '/dist/style.css.map'), 'utf8') + ); + + assert(raw.includes('/*# sourceMappingURL=/style.css.map */')); + assert.equal( + map.sourceRoot, + path.normalize('../integration/sourcemap-css') + ); + + let sourceMap = await new SourceMap().addMap(map); + assert.equal(Object.keys(sourceMap.sources).length, 1); + assert.equal(sourceMap.sources['style.css'], input); + + checkSourceMapping({ + map: sourceMap, + source: input, + generated: raw, + str: 'body', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: input, + generated: raw, + str: 'background-color', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + } + + await test(false); + await test(true); + }); + + it('should create a valid sourcemap for a CSS bundle with imports', async function() { + async function test(minify) { + let b = await bundle( + path.join(__dirname, '/integration/sourcemap-css-import/style.css'), + {minify} + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.css', 'other-style.css', 'another-style.css'], + childBundles: [ + { + name: 'style.css.map', + type: 'map' + } + ] + }); + + let style = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-css-import/style.css'), + 'utf8' + ); + let otherStyle = await fs.readFile( + path.join( + __dirname, + '/integration/sourcemap-css-import/other-style.css' + ), + 'utf8' + ); + let anotherStyle = await fs.readFile( + path.join( + __dirname, + '/integration/sourcemap-css-import/another-style.css' + ), + 'utf8' + ); + let raw = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + let map = JSON.parse( + await fs.readFile(path.join(__dirname, '/dist/style.css.map'), 'utf8') + ); + + assert(raw.includes('/*# sourceMappingURL=/style.css.map */')); + assert.equal( + map.sourceRoot, + path.normalize('../integration/sourcemap-css-import') + ); + + let sourceMap = await new SourceMap().addMap(map); + assert.equal(Object.keys(sourceMap.sources).length, 3); + assert.equal(sourceMap.sources['style.css'], style); + assert.equal(sourceMap.sources['other-style.css'], otherStyle); + assert.equal(sourceMap.sources['another-style.css'], anotherStyle); + + checkSourceMapping({ + map: sourceMap, + source: style, + generated: raw, + str: 'body', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: style, + generated: raw, + str: 'background-color', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: otherStyle, + generated: raw, + str: 'div', + sourcePath: 'other-style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: otherStyle, + generated: raw, + str: 'width', + sourcePath: 'other-style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: anotherStyle, + generated: raw, + str: 'main', + sourcePath: 'another-style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: anotherStyle, + generated: raw, + str: 'font-family', + sourcePath: 'another-style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + } + + await test(false); + await test(true); + }); + + it('should create a valid sourcemap for a SASS asset', async function() { + async function test(minify) { + let b = await bundle( + path.join(__dirname, '/integration/sourcemap-sass/style.scss'), + {minify} + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.scss'], + childBundles: [ + { + name: 'style.css.map', + type: 'map' + } + ] + }); + + let input = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-sass/style.scss'), + 'utf8' + ); + let raw = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + let map = JSON.parse( + await fs.readFile(path.join(__dirname, '/dist/style.css.map'), 'utf8') + ); + + assert(raw.includes('/*# sourceMappingURL=/style.css.map */')); + assert.equal( + map.sourceRoot, + path.normalize('../integration/sourcemap-sass') + ); + + let sourceMap = await new SourceMap().addMap(map); + assert.equal(Object.keys(sourceMap.sources).length, 1); + assert.equal(sourceMap.sources['style.scss'], input); + + checkSourceMapping({ + map: sourceMap, + source: input, + generated: raw, + str: 'body', + sourcePath: 'style.scss', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: input, + generated: raw, + str: 'color', + sourcePath: 'style.scss', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + } + + await test(false); + await test(true); + }); + + it('should create a valid sourcemap when for a CSS asset importing SASS', async function() { + async function test(minify) { + let b = await bundle( + path.join(__dirname, '/integration/sourcemap-sass-imported/style.css'), + {minify} + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.css', 'other.scss'], + childBundles: [ + { + name: 'style.css.map', + type: 'map' + } + ] + }); + + let style = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-sass-imported/style.css'), + 'utf8' + ); + let other = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-sass-imported/other.scss'), + 'utf8' + ); + let raw = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + let map = JSON.parse( + await fs.readFile(path.join(__dirname, '/dist/style.css.map'), 'utf8') + ); + + assert(raw.includes('/*# sourceMappingURL=/style.css.map */')); + assert.equal( + map.sourceRoot, + path.normalize('../integration/sourcemap-sass-imported') + ); + + let sourceMap = await new SourceMap().addMap(map); + assert.equal(Object.keys(sourceMap.sources).length, 2); + assert.equal(sourceMap.sources['style.css'], style); + assert.equal(sourceMap.sources['other.scss'], other); + + checkSourceMapping({ + map: sourceMap, + source: style, + generated: raw, + str: 'body', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: style, + generated: raw, + str: 'color', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: other, + generated: raw, + str: 'div', + sourcePath: 'other.scss', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: other, + generated: raw, + str: 'font-family', + sourcePath: 'other.scss', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + } + await test(false); + await test(true); + }); + + it('should create a valid sourcemap for a LESS asset', async function() { + async function test(minify) { + let b = await bundle( + path.join(__dirname, '/integration/sourcemap-less/style.less'), + {minify} + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.less'], + childBundles: [ + { + name: 'style.css.map', + type: 'map' + } + ] + }); + + let input = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-less/style.less'), + 'utf8' + ); + let raw = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + let map = JSON.parse( + await fs.readFile(path.join(__dirname, '/dist/style.css.map'), 'utf8') + ); + + assert(raw.includes('/*# sourceMappingURL=/style.css.map */')); + assert.equal( + map.sourceRoot, + path.normalize('../integration/sourcemap-less') + ); + + let sourceMap = await new SourceMap().addMap(map); + assert.equal(Object.keys(sourceMap.sources).length, 1); + assert.equal( + sourceMap.sources['style.less'], + input.replace(new RegExp(os.EOL, 'g'), '\n') + ); + + checkSourceMapping({ + map: sourceMap, + source: input, + generated: raw, + str: 'div', + sourcePath: 'style.less', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: input, + generated: raw, + str: 'width', + sourcePath: 'style.less', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + } + await test(false); + await test(true); + }); + + it('should load existing sourcemaps for CSS files', async function() { + async function test(minify) { + let b = await bundle( + path.join(__dirname, '/integration/sourcemap-css-existing/style.css'), + {minify} + ); + + await assertBundleTree(b, { + name: 'style.css', + assets: ['style.css', 'library.css'], + childBundles: [ + { + name: 'style.css.map', + type: 'map' + } + ] + }); + + let style = await fs.readFile( + path.join(__dirname, '/integration/sourcemap-css-existing/style.css'), + 'utf8' + ); + let library = await fs.readFile( + path.join( + __dirname, + '/integration/sourcemap-css-existing/test/library.raw.scss' + ), + 'utf8' + ); + let raw = await fs.readFile( + path.join(__dirname, '/dist/style.css'), + 'utf8' + ); + let map = JSON.parse( + await fs.readFile(path.join(__dirname, '/dist/style.css.map'), 'utf8') + ); + + assert(raw.includes('/*# sourceMappingURL=/style.css.map */')); + assert.equal( + map.sourceRoot, + path.normalize('../integration/sourcemap-css-existing') + ); + + let sourceMap = await new SourceMap().addMap(map); + assert.equal(Object.keys(sourceMap.sources).length, 2); + assert.equal(sourceMap.sources['style.css'], style); + assert.equal( + sourceMap.sources[path.normalize('test/library.scss')], + library.replace(new RegExp(os.EOL, 'g'), '\n') + ); + + checkSourceMapping({ + map: sourceMap, + source: style, + generated: raw, + str: 'main', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: style, + generated: raw, + str: 'display', + sourcePath: 'style.css', + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: library, + generated: raw, + str: 'body', + sourcePath: path.normalize('test/library.scss'), + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: library, + generated: raw, + str: 'div', + generatedStr: 'body div', + sourcePath: path.normalize('test/library.scss'), + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + + checkSourceMapping({ + map: sourceMap, + source: library, + generated: raw, + str: 'background-color', + sourcePath: path.normalize('test/library.scss'), + msg: ' ' + (minify ? 'with' : 'without') + ' minification' + }); + } + await test(false); + await test(true); + }); }); diff --git a/packages/core/integration-tests/test/stylus.js b/packages/core/integration-tests/test/stylus.js index 40458ed46ed..68f5a092c6a 100644 --- a/packages/core/integration-tests/test/stylus.js +++ b/packages/core/integration-tests/test/stylus.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('stylus', function() { it('should support requiring stylus files', async function() { @@ -17,7 +17,11 @@ describe('stylus', function() { { name: 'index.css', assets: ['index.styl'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -50,7 +54,11 @@ describe('stylus', function() { { name: 'index.css', assets: ['index.styl'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); @@ -84,7 +92,11 @@ describe('stylus', function() { { name: 'index.css', assets: ['index.styl'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] }, { type: 'woff2', @@ -132,20 +144,24 @@ describe('stylus', function() { { name: 'index.css', assets: ['index.styl'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); let output = await run(b); assert.equal(typeof output, 'function'); - assert.equal(output(), '_index_g9mqo_1'); + assert(output().startsWith('_index_')); let css = await fs.readFile( path.join(__dirname, '/dist/index.css'), 'utf8' ); - assert(css.includes('._index_g9mqo_1')); + assert(css.includes('._index_')); }); it('should support requiring stylus files with glob dependencies', async function() { @@ -163,7 +179,11 @@ describe('stylus', function() { { name: 'index.css', assets: ['index.styl'], - childBundles: [] + childBundles: [ + { + type: 'map' + } + ] } ] }); diff --git a/packages/core/integration-tests/test/sugarss.js b/packages/core/integration-tests/test/sugarss.js index 6f0e18994df..f46fec12cb6 100644 --- a/packages/core/integration-tests/test/sugarss.js +++ b/packages/core/integration-tests/test/sugarss.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const {bundle, assertBundleTree} = require('./utils'); +const {bundle, assertBundleTree} = require('@parcel/test-utils'); const fs = require('@parcel/fs'); const path = require('path'); diff --git a/packages/core/integration-tests/test/typescript.js b/packages/core/integration-tests/test/typescript.js index 7aaada0a37b..ef792f5563e 100644 --- a/packages/core/integration-tests/test/typescript.js +++ b/packages/core/integration-tests/test/typescript.js @@ -1,7 +1,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('@parcel/fs'); -const {bundle, run, assertBundleTree} = require('./utils'); +const {bundle, run, assertBundleTree} = require('@parcel/test-utils'); describe('typescript', function() { it('should produce a ts bundle using ES6 imports', async function() { diff --git a/packages/core/integration-tests/test/utils.js b/packages/core/integration-tests/test/utils.js index e2f78fd517a..25ebdb32d9e 100644 --- a/packages/core/integration-tests/test/utils.js +++ b/packages/core/integration-tests/test/utils.js @@ -1,283 +1,5 @@ -const Bundler = require('parcel-bundler'); -const assert = require('assert'); -const vm = require('vm'); -const fs = require('@parcel/fs'); -const nodeFS = require('fs'); -const path = require('path'); -const WebSocket = require('ws'); -const Module = require('module'); - -const {promisify} = require('@parcel/utils'); -const {sleep} = require('@parcel/test-utils'); -const rimraf = promisify(require('rimraf')); -const ncp = promisify(require('ncp')); - -const chalk = new (require('chalk')).constructor({enabled: true}); -const warning = chalk.keyword('orange'); -// eslint-disable-next-line no-console -console.warn = (...args) => { - // eslint-disable-next-line no-console - console.error(warning(...args)); -}; - -async function removeDistDirectory(count = 0) { - try { - await rimraf(path.join(__dirname, 'dist')); - } catch (e) { - if (count > 8) { - // eslint-disable-next-line no-console - console.warn('WARNING: Unable to remove dist directory:', e.message); - return; - } - - await sleep(250); - await removeDistDirectory(count + 1); - } -} +const {removeDistDirectory} = require('@parcel/test-utils'); beforeEach(async function() { await removeDistDirectory(); }); - -function bundler(file, opts) { - return new Bundler( - file, - Object.assign( - { - outDir: path.join(__dirname, 'dist'), - watch: false, - cache: false, - killWorkers: false, - hmr: false, - logLevel: 0, - throwErrors: true - }, - opts - ) - ); -} - -function bundle(file, opts) { - return bundler(file, opts).bundle(); -} - -function prepareBrowserContext(bundle, globals) { - // for testing dynamic imports - const fakeElement = { - remove() {} - }; - - const fakeDocument = { - createElement(tag) { - return {tag}; - }, - - getElementsByTagName() { - return [ - { - appendChild(el) { - setTimeout(function() { - if (el.tag === 'script') { - vm.runInContext( - nodeFS.readFileSync( - path.join(path.dirname(bundle.name), el.src) - ), - ctx - ); - } - - el.onload(); - }, 0); - } - } - ]; - }, - - getElementById() { - return fakeElement; - }, - - body: { - appendChild() { - return null; - } - } - }; - - var exports = {}; - var ctx = Object.assign( - { - exports, - module: {exports}, - document: fakeDocument, - WebSocket, - console, - location: {hostname: 'localhost'}, - fetch(url) { - return Promise.resolve({ - arrayBuffer() { - return Promise.resolve( - new Uint8Array( - nodeFS.readFileSync(path.join(path.dirname(bundle.name), url)) - ).buffer - ); - }, - text() { - return Promise.resolve( - nodeFS.readFileSync( - path.join(path.dirname(bundle.name), url), - 'utf8' - ) - ); - } - }); - } - }, - globals - ); - - ctx.window = ctx; - return ctx; -} - -function prepareNodeContext(bundle, globals) { - var mod = new Module(bundle.name); - mod.paths = [path.dirname(bundle.name) + '/node_modules']; - - var ctx = Object.assign( - { - module: mod, - exports: module.exports, - __filename: bundle.name, - __dirname: path.dirname(bundle.name), - require: function(path) { - return mod.require(path); - }, - console, - process: process, - setTimeout: setTimeout, - setImmediate: setImmediate - }, - globals - ); - - ctx.global = ctx; - return ctx; -} - -async function run(bundle, globals, opts = {}) { - var ctx; - switch (bundle.entryAsset.options.target) { - case 'browser': - ctx = prepareBrowserContext(bundle, globals); - break; - case 'node': - ctx = prepareNodeContext(bundle, globals); - break; - case 'electron': - ctx = Object.assign( - prepareBrowserContext(bundle, globals), - prepareNodeContext(bundle, globals) - ); - break; - } - - vm.createContext(ctx); - vm.runInContext(await fs.readFile(bundle.name), ctx); - - if (opts.require !== false) { - if (ctx.parcelRequire) { - return ctx.parcelRequire(bundle.entryAsset.id); - } else if (ctx.output) { - return ctx.output; - } - if (ctx.module) { - return ctx.module.exports; - } - } - - return ctx; -} - -async function assertBundleTree(bundle, tree) { - if (tree.name) { - assert.equal( - path.basename(bundle.name), - tree.name, - 'bundle names mismatched' - ); - } - - if (tree.type) { - assert.equal( - bundle.type.toLowerCase(), - tree.type.toLowerCase(), - 'bundle types mismatched' - ); - } - - if (tree.assets) { - assert.deepEqual( - Array.from(bundle.assets) - .map(a => a.basename) - .sort(), - tree.assets.sort() - ); - } - - let childBundles = Array.isArray(tree) ? tree : tree.childBundles; - if (childBundles) { - let children = Array.from(bundle.childBundles).sort( - (a, b) => - Array.from(a.assets).sort()[0].basename < - Array.from(b.assets).sort()[0].basename - ? -1 - : 1 - ); - assert.equal( - bundle.childBundles.size, - childBundles.length, - 'expected number of child bundles mismatched' - ); - await Promise.all( - childBundles.map((b, i) => assertBundleTree(children[i], b)) - ); - } - - if (/js|css/.test(bundle.type)) { - assert(await fs.exists(bundle.name), 'expected file does not exist'); - } -} - -function nextBundle(b) { - return new Promise(resolve => { - b.once('bundled', resolve); - }); -} - -function deferred() { - let resolve, reject; - let promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - - promise.resolve = resolve; - promise.reject = reject; - - return promise; -} - -function normaliseNewlines(text) { - return text.replace(/(\r\n|\n|\r)/g, '\n'); -} - -exports.bundler = bundler; -exports.bundle = bundle; -exports.run = run; -exports.assertBundleTree = assertBundleTree; -exports.nextBundle = nextBundle; -exports.deferred = deferred; -exports.rimraf = rimraf; -exports.ncp = ncp; -exports.normaliseNewlines = normaliseNewlines; diff --git a/packages/core/integration-tests/test/vue.js b/packages/core/integration-tests/test/vue.js index c53dbb5220f..b27c5a7b4ea 100644 --- a/packages/core/integration-tests/test/vue.js +++ b/packages/core/integration-tests/test/vue.js @@ -1,6 +1,6 @@ const assert = require('assert'); const path = require('path'); -const {bundle, assertBundleTree, run} = require('./utils'); +const {bundle, assertBundleTree, run} = require('@parcel/test-utils'); const fs = require('@parcel/fs'); describe('vue', function() { diff --git a/packages/core/integration-tests/test/wasm.js b/packages/core/integration-tests/test/wasm.js index 9fef9ef40cc..67a8eb51fcb 100644 --- a/packages/core/integration-tests/test/wasm.js +++ b/packages/core/integration-tests/test/wasm.js @@ -1,6 +1,6 @@ const assert = require('assert'); const path = require('path'); -const {bundle, run, assertBundleTree, deferred} = require('./utils'); +const {bundle, run, assertBundleTree, deferred} = require('@parcel/test-utils'); describe('wasm', function() { if (typeof WebAssembly === 'undefined') { diff --git a/packages/core/integration-tests/test/watcher.js b/packages/core/integration-tests/test/watcher.js index 58bee8fb362..3426d3defc1 100644 --- a/packages/core/integration-tests/test/watcher.js +++ b/packages/core/integration-tests/test/watcher.js @@ -9,8 +9,9 @@ const { nextBundle, rimraf, ncp -} = require('./utils'); +} = require('@parcel/test-utils'); const {sleep} = require('@parcel/test-utils'); +const {symlinkPrivilegeWarning} = require('@parcel/test-utils'); const {symlinkSync} = require('fs'); const inputDir = path.join(__dirname, '/input'); @@ -274,29 +275,36 @@ describe('watcher', function() { inputDir ); - // Create the symlink here to prevent cross platform and git issues - symlinkSync( - path.join(inputDir, 'local.js'), - path.join(inputDir, 'src/symlinked_local.js') - ); - - b = bundler(path.join(inputDir, '/src/index.js'), { - watch: true - }); - - let bundle = await b.bundle(); - let output = await run(bundle); - - assert.equal(output(), 3); - - await sleep(100); - fs.writeFile( - path.join(inputDir, '/local.js'), - 'exports.a = 5; exports.b = 5;' - ); - - bundle = await nextBundle(b); - output = await run(bundle); - assert.equal(output(), 10); + try { + // Create the symlink here to prevent cross platform and git issues + symlinkSync( + path.join(inputDir, 'local.js'), + path.join(inputDir, 'src/symlinked_local.js') + ); + + b = bundler(path.join(inputDir, '/src/index.js'), { + watch: true + }); + + let bundle = await b.bundle(); + let output = await run(bundle); + + assert.equal(output(), 3); + + await sleep(100); + fs.writeFile( + path.join(inputDir, '/local.js'), + 'exports.a = 5; exports.b = 5;' + ); + + bundle = await nextBundle(b); + output = await run(bundle); + assert.equal(output(), 10); + } catch (e) { + if (e.code == 'EPERM') { + symlinkPrivilegeWarning(); + this.skip(); + } + } }); }); diff --git a/packages/core/logger/package.json b/packages/core/logger/package.json index dccdb33fba4..bedcd5e884e 100644 --- a/packages/core/logger/package.json +++ b/packages/core/logger/package.json @@ -11,6 +11,9 @@ "engines": { "node": ">= 6.0.0" }, + "publishConfig": { + "access": "public" + }, "scripts": { "test": "cross-env NODE_ENV=test mocha", "test-ci": "yarn build && yarn test", diff --git a/packages/core/parcel-bundler/bin/cli.js b/packages/core/parcel-bundler/bin/cli.js index bcf058add51..0a63cdf59b4 100755 --- a/packages/core/parcel-bundler/bin/cli.js +++ b/packages/core/parcel-bundler/bin/cli.js @@ -1,5 +1,7 @@ #!/usr/bin/env node +process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || 16; + // Node 8 supports native async functions - no need to use compiled code! module.exports = parseInt(process.versions.node, 10) < 8 diff --git a/packages/core/parcel-bundler/package.json b/packages/core/parcel-bundler/package.json index 1e819659199..5fc36a96dd5 100644 --- a/packages/core/parcel-bundler/package.json +++ b/packages/core/parcel-bundler/package.json @@ -41,6 +41,7 @@ "command-exists": "^1.2.6", "commander": "^2.11.0", "cross-spawn": "^6.0.4", + "css-modules-loader-core": "^1.1.0", "cssnano": "^4.0.0", "deasync": "^0.1.14", "dotenv": "^5.0.0", @@ -48,7 +49,7 @@ "fast-glob": "^2.2.2", "filesize": "^3.6.0", "get-port": "^3.2.0", - "htmlnano": "^0.1.9", + "htmlnano": "^0.2.2", "is-glob": "^4.0.0", "is-url": "^1.2.2", "js-yaml": "^3.10.0", @@ -58,7 +59,7 @@ "node-forge": "^0.7.1", "node-libs-browser": "^2.0.0", "opn": "^5.1.0", - "postcss": "^7.0.5", + "postcss": "^7.0.11", "postcss-value-parser": "^3.3.1", "posthtml": "^0.11.2", "posthtml-parser": "^0.4.0", diff --git a/packages/core/parcel-bundler/src/Asset.js b/packages/core/parcel-bundler/src/Asset.js index 3b3c11a7668..381d6e9f817 100644 --- a/packages/core/parcel-bundler/src/Asset.js +++ b/packages/core/parcel-bundler/src/Asset.js @@ -34,6 +34,7 @@ class Asset { this.ast = null; this.generated = null; this.hash = null; + this.sourceMaps = null; this.parentDeps = new Set(); this.dependencies = new Map(); this.depAssets = new Map(); @@ -84,16 +85,7 @@ class Asset { this.dependencies.set(name, Object.assign({name}, opts)); } - addURLDependency(url, from = this.name, opts) { - if (!url || isURL(url)) { - return url; - } - - if (typeof from === 'object') { - opts = from; - from = this.name; - } - + resolveDependency(url, from = this.name) { const parsed = URL.parse(url); let depName; let resolved; @@ -110,8 +102,24 @@ class Asset { depName = './' + path.relative(path.dirname(this.name), resolved); } + return {depName, resolved}; + } + + addURLDependency(url, from = this.name, opts) { + if (!url || isURL(url)) { + return url; + } + + if (typeof from === 'object') { + opts = from; + from = this.name; + } + + const {depName, resolved} = this.resolveDependency(url, from); + this.addDependency(depName, Object.assign({dynamic: true, resolved}, opts)); + const parsed = URL.parse(url); parsed.pathname = this.options.parser .getAsset(resolved, this.options) .generateBundleName(); @@ -152,7 +160,7 @@ class Asset { return conf; } - return await config.load(opts.path || this.name, filenames); + return config.load(opts.path || this.name, filenames); } return null; @@ -163,7 +171,7 @@ class Asset { } async load() { - return await fs.readFile(this.name, this.encoding); + return fs.readFile(this.name, this.encoding); } parse() { @@ -249,7 +257,8 @@ class Asset { // Replace temporary bundle names in the output with the final content-hashed names. let newValue = value; for (let [name, map] of bundleNameMap) { - newValue = newValue.split(name).join(map); + let mapRelative = path.relative(path.dirname(this.relativeName), map); + newValue = newValue.split(name).join(mapRelative); } // Copy `this.generated` on write so we don't end up writing the final names to the cache. diff --git a/packages/core/parcel-bundler/src/Bundle.js b/packages/core/parcel-bundler/src/Bundle.js index e4dbf718776..478693c3030 100644 --- a/packages/core/parcel-bundler/src/Bundle.js +++ b/packages/core/parcel-bundler/src/Bundle.js @@ -39,6 +39,14 @@ class Bundle { addAsset(asset) { asset.bundles.add(this); this.assets.add(asset); + if ( + this.type != 'map' && + this.type == asset.type && + asset.options.sourceMaps && + asset.sourceMaps + ) { + this.getSiblingBundle('map').addAsset(asset); + } } removeAsset(asset) { @@ -46,12 +54,12 @@ class Bundle { this.assets.delete(asset); } - addOffset(asset, line) { - this.offsets.set(asset, line); + addOffset(asset, line, column = 0) { + this.offsets.set(asset, [line, column]); } getOffset(asset) { - return this.offsets.get(asset) || 0; + return this.offsets.get(asset) || [0, 0]; } getSiblingBundle(type) { @@ -64,7 +72,11 @@ class Bundle { type, Path.join( Path.dirname(this.name), - Path.basename(this.name, Path.extname(this.name)) + '.' + type + // keep the original extension for source map files, so we have + // .js.map instead of just .map + type === 'map' + ? Path.basename(this.name) + '.' + type + : Path.basename(this.name, Path.extname(this.name)) + '.' + type ), this ); @@ -110,12 +122,23 @@ class Bundle { getHashedBundleName(contentHash) { // If content hashing is enabled, generate a hash from all assets in the bundle. // Otherwise, use a hash of the filename so it remains consistent across builds. - let ext = Path.extname(this.name); + + if (this.type == 'map') { + return this.parentBundle.getHashedBundleName(contentHash) + '.map'; + } + + let basename = Path.basename(this.name); + + let ext = Path.extname(basename); let hash = (contentHash ? this.getHash() : Path.basename(this.name, ext) ).slice(-8); - let entryAsset = this.entryAsset || this.parentBundle.entryAsset; + let entryAsset = this; + while (!entryAsset.entryAsset && entryAsset.parentBundle) { + entryAsset = entryAsset.parentBundle; + } + entryAsset = entryAsset.entryAsset; let name = Path.basename(entryAsset.name, Path.extname(entryAsset.name)); let isMainEntry = entryAsset.options.entryFiles[0] === entryAsset.name; let isEntry = diff --git a/packages/core/parcel-bundler/src/Bundler.js b/packages/core/parcel-bundler/src/Bundler.js index f82222bf88f..2806990608c 100644 --- a/packages/core/parcel-bundler/src/Bundler.js +++ b/packages/core/parcel-bundler/src/Bundler.js @@ -19,7 +19,7 @@ const installPackage = require('./utils/installPackage'); const bundleReport = require('./utils/bundleReport'); const prettifyTime = require('./utils/prettifyTime'); const getRootDir = require('./utils/getRootDir'); -const {glob} = require('./utils/glob'); +const {glob, isGlob} = require('./utils/glob'); /** * The Bundler is the main entry point. It resolves and loads assets, @@ -29,7 +29,9 @@ class Bundler extends EventEmitter { constructor(entryFiles, options = {}) { super(); - this.entryFiles = this.normalizeEntries(entryFiles); + entryFiles = this.normalizeEntries(entryFiles); + this.watchedGlobs = entryFiles.filter(entry => isGlob(entry)); + this.entryFiles = this.findEntryFiles(entryFiles); this.options = this.normalizeOptions(options); this.resolver = new Resolver(this.options); @@ -82,6 +84,10 @@ class Bundler extends EventEmitter { entryFiles = [process.cwd()]; } + return entryFiles; + } + + findEntryFiles(entryFiles) { // Match files as globs return entryFiles .reduce((p, m) => p.concat(glob.sync(m)), []) @@ -372,7 +378,12 @@ class Bundler extends EventEmitter { if (process.env.NODE_ENV === 'test' && !this.watcher.ready) { await new Promise(resolve => this.watcher.once('ready', resolve)); } + this.watchedGlobs.forEach(glob => { + this.watcher.add(glob); + }); + this.watcher.on('add', this.onAdd.bind(this)); this.watcher.on('change', this.onChange.bind(this)); + this.watcher.on('unlink', this.onUnlink.bind(this)); } if (this.options.hmr) { @@ -479,7 +490,7 @@ class Bundler extends EventEmitter { this.options.autoinstall && install ) { - return await this.installDep(asset, dep); + return this.installDep(asset, dep); } err.message = `Cannot resolve dependency '${dep.name}'`; @@ -510,7 +521,7 @@ class Bundler extends EventEmitter { } } - return await this.resolveDep(asset, dep, false); + return this.resolveDep(asset, dep, false); } async throwDepError(asset, dep, err) { @@ -561,6 +572,7 @@ class Bundler extends EventEmitter { asset.buildTime = asset.endTime - asset.startTime; asset.id = processed.id; asset.generated = processed.generated; + asset.sourceMaps = processed.sourceMaps; asset.hash = processed.hash; asset.cacheData = processed.cacheData; @@ -593,6 +605,13 @@ class Bundler extends EventEmitter { }) ); + // If there was a processing error, re-throw now that we've set up + // depdenency watchers. This keeps reloading working if there is an + // error in a dependency not directly handled by Parcel. + if (processed.error !== null) { + throw processed.error; + } + // Store resolved assets in their original order dependencies.forEach((dep, i) => { asset.dependencies.set(dep.name, dep); @@ -751,7 +770,24 @@ class Bundler extends EventEmitter { } } + async onAdd(path) { + path = Path.join(process.cwd(), path); + + let asset = this.parser.getAsset(path, this.options); + this.loadedAssets.set(path, asset); + + this.entryAssets.add(asset); + + await this.watch(path, asset); + this.onChange(path); + } + async onChange(path) { + // The path to the newly-added items are not absolute. + if (!Path.isAbsolute(path)) { + path = Path.resolve(process.cwd(), path); + } + let assets = this.watchedAssets.get(path); if (!assets || !assets.size) { return; @@ -772,6 +808,19 @@ class Bundler extends EventEmitter { }, 100); } + async onUnlink(path) { + // The path to the newly-added items are not absolute. + if (!Path.isAbsolute(path)) { + path = Path.resolve(process.cwd(), path); + } + + let asset = this.getLoadedAsset(path); + this.entryAssets.delete(asset); + this.unloadAsset(asset); + + this.bundle(); + } + middleware() { this.bundle(); return Server.middleware(this); diff --git a/packages/core/parcel-bundler/src/FSCache.js b/packages/core/parcel-bundler/src/FSCache.js index dedee45b2ea..80fbd4b8197 100644 --- a/packages/core/parcel-bundler/src/FSCache.js +++ b/packages/core/parcel-bundler/src/FSCache.js @@ -7,7 +7,14 @@ const logger = require('@parcel/logger'); const {isGlob, glob} = require('./utils/glob'); // These keys can affect the output, so if they differ, the cache should not match -const OPTION_KEYS = ['publicURL', 'minify', 'hmr', 'target', 'scopeHoist']; +const OPTION_KEYS = [ + 'publicURL', + 'minify', + 'hmr', + 'target', + 'scopeHoist', + 'sourceMaps' +]; class FSCache { constructor(options) { diff --git a/packages/core/parcel-bundler/src/Parser.js b/packages/core/parcel-bundler/src/Parser.js index 09163cb6d4a..2042e19171f 100644 --- a/packages/core/parcel-bundler/src/Parser.js +++ b/packages/core/parcel-bundler/src/Parser.js @@ -51,6 +51,7 @@ class Parser { this.registerExtension('jade', './assets/PugAsset'); this.registerExtension('pug', './assets/PugAsset'); + this.registerExtension('md', './assets/MarkdownAsset'); let extensions = options.extensions || {}; for (let ext in extensions) { diff --git a/packages/core/parcel-bundler/src/Pipeline.js b/packages/core/parcel-bundler/src/Pipeline.js index 03a7579492f..6377259b123 100644 --- a/packages/core/parcel-bundler/src/Pipeline.js +++ b/packages/core/parcel-bundler/src/Pipeline.js @@ -1,5 +1,6 @@ const Parser = require('./Parser'); const path = require('path'); +const {errorUtils} = require('@parcel/utils'); /** * A Pipeline composes multiple Asset types together. @@ -17,16 +18,24 @@ class Pipeline { } let asset = this.parser.getAsset(path, options); - let generated = await this.processAsset(asset); + let error = null; let generatedMap = {}; - for (let rendition of generated) { - generatedMap[rendition.type] = rendition.value; + try { + let generated = await this.processAsset(asset); + for (let rendition of generated) { + generatedMap[rendition.type] = rendition.value; + } + } catch (err) { + error = errorUtils.errorToJson(err); + error.fileName = path; } return { id: asset.id, dependencies: Array.from(asset.dependencies.values()), generated: generatedMap, + sourceMaps: asset.sourceMaps, + error: error, hash: asset.hash, cacheData: asset.cacheData }; @@ -41,7 +50,6 @@ class Pipeline { let inputType = path.extname(asset.name).slice(1); let generated = []; - for (let rendition of this.iterateRenditions(asset)) { let {type, value} = rendition; if (typeof value !== 'string' || rendition.final) { @@ -84,6 +92,19 @@ class Pipeline { throw asset.generateErrorMessage(err); } + let hasMap = false; + let sourceMaps = {}; + for (let rendition of generated) { + if (rendition.map && rendition.type == asset.type) { + sourceMaps[rendition.type] = rendition.map; + hasMap = true; + } + } + + if (hasMap) { + asset.sourceMaps = sourceMaps; + } + asset.generated = generated; asset.hash = await asset.generateHash(); diff --git a/packages/core/parcel-bundler/src/Resolver.js b/packages/core/parcel-bundler/src/Resolver.js index d5037782eaa..825e45d45ef 100755 --- a/packages/core/parcel-bundler/src/Resolver.js +++ b/packages/core/parcel-bundler/src/Resolver.js @@ -4,6 +4,7 @@ const path = require('path'); const {isGlob} = require('./utils/glob'); const fs = require('@parcel/fs'); const micromatch = require('micromatch'); +const getModuleParts = require('./utils/getModuleParts'); const EMPTY_SHIM = require.resolve('./builtins/_empty'); @@ -104,7 +105,7 @@ class Resolver { // If we couldn't resolve the node_modules path, just return the module name info if (!resolved) { - let parts = this.getModuleParts(filename); + let parts = getModuleParts(filename); resolved = { moduleName: parts[0], subPath: parts[1] @@ -158,7 +159,7 @@ class Resolver { // First try as a file, then as a directory. return ( (await this.loadAsFile(filename, extensions, pkg)) || - (await this.loadDirectory(filename, extensions, pkg)) + (await this.loadDirectory(filename, extensions, pkg)) // eslint-disable-line no-return-await ); } @@ -171,7 +172,7 @@ class Resolver { return {filePath: builtins[filename]}; } - let parts = this.getModuleParts(filename); + let parts = getModuleParts(filename); let root = path.parse(dir).root; while (dir !== root) { @@ -250,7 +251,7 @@ class Resolver { } // Fall back to an index file inside the directory. - return await this.loadAsFile(path.join(dir, 'index'), extensions, pkg); + return this.loadAsFile(path.join(dir, 'index'), extensions, pkg); } async readPackage(dir) { @@ -374,7 +375,7 @@ class Resolver { alias = this.lookupAlias(aliases, filename, dir); if (alias == null) { // If it didn't match, try only the module name. - let parts = this.getModuleParts(filename); + let parts = getModuleParts(filename); alias = this.lookupAlias(aliases, parts[0], dir); if (typeof alias === 'string') { // Append the filename back onto the aliased module. @@ -405,6 +406,10 @@ class Resolver { } } } + // Or try a lookup replacing backslash characters with forward slash + if (alias == null && ~filename.indexOf('\\')) { + alias = aliases[filename.replace(/\\/g, '/')]; + } } if (typeof alias === 'string') { @@ -438,16 +443,6 @@ class Resolver { let pkg = await this.findPackage(dir); return this.resolveAliases(filename, pkg); } - - getModuleParts(name) { - let parts = path.normalize(name).split(path.sep); - if (parts[0].charAt(0) === '@') { - // Scoped module (e.g. @scope/module). Merge the first two parts back together. - parts.splice(0, 2, `${parts[0]}/${parts[1]}`); - } - - return parts; - } } module.exports = Resolver; diff --git a/packages/core/parcel-bundler/src/Server.js b/packages/core/parcel-bundler/src/Server.js index dfe81f892fc..8e87fbc9a40 100644 --- a/packages/core/parcel-bundler/src/Server.js +++ b/packages/core/parcel-bundler/src/Server.js @@ -36,7 +36,8 @@ function middleware(bundler) { const serve = serveStatic(bundler.options.outDir, { index: false, redirect: false, - setHeaders: setHeaders + setHeaders: setHeaders, + dotfiles: 'allow' }); return function(req, res, next) { @@ -78,6 +79,7 @@ function middleware(bundler) { } function send500(error) { + setHeaders(res); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.writeHead(500); let errorMesssge = '

🚨 Build Error

'; @@ -105,7 +107,7 @@ function middleware(bundler) { if (next) { return next(); } - + setHeaders(res); res.writeHead(404); res.end(); } diff --git a/packages/core/parcel-bundler/src/SourceMap.js b/packages/core/parcel-bundler/src/SourceMap.js index 82a3a7ba7e3..222dc3c3365 100644 --- a/packages/core/parcel-bundler/src/SourceMap.js +++ b/packages/core/parcel-bundler/src/SourceMap.js @@ -10,20 +10,20 @@ class SourceMap { purifyMappings(mappings) { if (Array.isArray(mappings)) { - return mappings.filter(mapping => { - return ( + return mappings.filter( + mapping => mapping && - mapping.source && - mapping.original && - typeof mapping.original.line === 'number' && - mapping.original.line > 0 && - typeof mapping.original.column === 'number' && + (typeof mapping.original === 'object' && + (mapping.original === null || + (typeof mapping.original.line === 'number' && + mapping.original.line > 0 && + typeof mapping.original.column === 'number' && + mapping.source))) && mapping.generated && typeof mapping.generated.line === 'number' && mapping.generated.line > 0 && typeof mapping.generated.column === 'number' - ); - }); + ); } return []; @@ -34,12 +34,14 @@ class SourceMap { return map; } map = typeof map === 'string' ? JSON.parse(map) : map; - return await new SourceMapConsumer(map); + if (map.sourceRoot) delete map.sourceRoot; + return new SourceMapConsumer(map); } async addMap(map, lineOffset = 0, columnOffset = 0) { - if (!(map instanceof SourceMap) && map.version) { + if (typeof map === 'string' || (typeof map === 'object' && map.version)) { let consumer = await this.getConsumer(map); + if (!consumer) return this; consumer.eachMapping(mapping => { this.addConsumerMapping(mapping, lineOffset, columnOffset); @@ -55,7 +57,7 @@ class SourceMap { // Only needs to happen in source-map 0.7 consumer.destroy(); } - } else { + } else if (map.mappings && map.sources) { if (!map.eachMapping) { map = new SourceMap(map.mappings, map.sources); } @@ -91,21 +93,22 @@ class SourceMap { } addConsumerMapping(mapping, lineOffset = 0, columnOffset = 0) { + let original = null; if ( - !mapping.source || - !mapping.originalLine || - (!mapping.originalColumn && mapping.originalColumn !== 0) + typeof mapping.originalLine === 'number' && + mapping.originalLine > 0 && + typeof mapping.originalColumn === 'number' ) { - return; + original = { + line: mapping.originalLine, + column: mapping.originalColumn + }; } this.mappings.push({ - source: mapping.source, + source: original ? mapping.source : null, name: mapping.name, - original: { - line: mapping.originalLine, - column: mapping.originalColumn - }, + original, generated: { line: mapping.generatedLine + lineOffset, column: mapping.generatedColumn + columnOffset @@ -340,7 +343,6 @@ class SourceMap { stringify(file, sourceRoot) { let generator = new SourceMapGenerator({file, sourceRoot}); - this.eachMapping(mapping => generator.addMapping(mapping)); Object.keys(this.sources).forEach(sourceName => generator.setSourceContent(sourceName, this.sources[sourceName]) diff --git a/packages/core/parcel-bundler/src/assets/CSSAsset.js b/packages/core/parcel-bundler/src/assets/CSSAsset.js index 8c3b7cde3fb..967457c82f9 100644 --- a/packages/core/parcel-bundler/src/assets/CSSAsset.js +++ b/packages/core/parcel-bundler/src/assets/CSSAsset.js @@ -3,27 +3,38 @@ const postcss = require('postcss'); const valueParser = require('postcss-value-parser'); const postcssTransform = require('../transforms/postcss'); const CssSyntaxError = require('postcss/lib/css-syntax-error'); +const SourceMap = require('../SourceMap'); +const loadSourceMap = require('../utils/loadSourceMap'); +const path = require('path'); const URL_RE = /url\s*\("?(?![a-z]+:)/; const IMPORT_RE = /@import/; +const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/; +const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/; const PROTOCOL_RE = /^[a-z]+:/; class CSSAsset extends Asset { constructor(name, options) { super(name, options); this.type = 'css'; + this.previousSourceMap = this.options.rendition + ? this.options.rendition.map + : null; } mightHaveDependencies() { return ( !/\.css$/.test(this.name) || IMPORT_RE.test(this.contents) || + COMPOSES_RE.test(this.contents) || URL_RE.test(this.contents) ); } parse(code) { - let root = postcss.parse(code, {from: this.name, to: this.name}); + let root = postcss.parse(code, { + from: this.name + }); return new CSSAst(code, root); } @@ -90,9 +101,29 @@ class CSSAsset extends Asset { this.ast.dirty = true; } } + + if (decl.prop === 'composes' && FROM_IMPORT_RE.test(decl.value)) { + let parsed = valueParser(decl.value); + + parsed.walk(node => { + if (node.type === 'string') { + const [, importPath] = FROM_IMPORT_RE.exec(decl.value); + this.addURLDependency(importPath, { + dynamic: false, + loc: decl.source.start + }); + } + }); + } }); } + async pretransform() { + if (this.options.sourceMaps && !this.previousSourceMap) { + this.previousSourceMap = await loadSourceMap(this); + } + } + async transform() { await postcssTransform(this); } @@ -100,14 +131,24 @@ class CSSAsset extends Asset { getCSSAst() { // Converts the ast to a CSS ast if needed, so we can apply postcss transforms. if (!(this.ast instanceof CSSAst)) { - this.ast = CSSAsset.prototype.parse.call(this, this.ast.render()); + this.ast = CSSAsset.prototype.parse.call( + this, + this.ast.render(this.name) + ); } return this.ast.root; } - generate() { - let css = this.ast ? this.ast.render() : this.contents; + async generate() { + let css; + if (this.ast) { + let result = this.ast.render(this.name); + css = result.css; + if (result.map) this.sourceMap = result.map; + } else { + css = this.contents; + } let js = ''; if (this.options.hmr) { @@ -125,11 +166,41 @@ class CSSAsset extends Asset { 'module.exports = ' + JSON.stringify(this.cssModules, null, 2) + ';'; } + if (this.options.sourceMaps) { + if (this.sourceMap) { + this.sourceMap = await new SourceMap().addMap(this.sourceMap); + } + + if (this.previousSourceMap) { + this.previousSourceMap.sources = this.previousSourceMap.sources.map(v => + path.join( + path.dirname(this.relativeName), + this.previousSourceMap.sourceRoot || '', + v + ) + ); + if (this.sourceMap) { + this.sourceMap = await new SourceMap().extendSourceMap( + this.previousSourceMap, + this.sourceMap + ); + } else { + this.sourceMap = await new SourceMap().addMap(this.previousSourceMap); + } + } else if (!this.sourceMap) { + this.sourceMap = new SourceMap().generateEmptyMap( + this.relativeName, + css + ); + } + } + return [ { type: 'css', value: css, - cssModules: this.cssModules + cssModules: this.cssModules, + map: this.sourceMap }, { type: 'js', @@ -172,13 +243,24 @@ class CSSAst { this.dirty = false; } - render() { + render(name) { if (this.dirty) { - this.css = ''; - postcss.stringify(this.root, c => (this.css += c)); + let {css, map} = this.root.toResult({ + to: name, + map: {inline: false, annotation: false, sourcesContent: true} + }); + + this.css = css; + + return { + css: this.css, + map: map ? map.toJSON() : null + }; } - return this.css; + return { + css: this.css + }; } } diff --git a/packages/core/parcel-bundler/src/assets/ElmAsset.js b/packages/core/parcel-bundler/src/assets/ElmAsset.js index 7a2309b3a5f..5f81ebc1260 100644 --- a/packages/core/parcel-bundler/src/assets/ElmAsset.js +++ b/packages/core/parcel-bundler/src/assets/ElmAsset.js @@ -44,12 +44,7 @@ class ElmAsset extends Asset { options.optimize = true; } - let compiled = await this.elm.compileToString(this.name, options); - this.contents = compiled.toString(); - if (this.options.hmr) { - let {inject} = await localRequire('elm-hot', this.name); - this.contents = inject(this.contents); - } + this.elmOpts = options; } async collectDependencies() { @@ -76,6 +71,13 @@ class ElmAsset extends Asset { } async generate() { + let compiled = await this.elm.compileToString(this.name, this.elmOpts); + this.contents = compiled.toString(); + if (this.options.hmr) { + let {inject} = await localRequire('elm-hot', this.name); + this.contents = inject(this.contents); + } + let output = this.contents; if (this.options.minify) { @@ -129,6 +131,13 @@ class ElmAsset extends Asset { return result.code; } } + + generateErrorMessage(err) { + // The generated stack is not useful, but other code may + // expect it and try to print it, so make it an empty string. + err.stack = ''; + return err; + } } module.exports = ElmAsset; diff --git a/packages/core/parcel-bundler/src/assets/GLSLAsset.js b/packages/core/parcel-bundler/src/assets/GLSLAsset.js index fc69a96cf88..55bf7111d52 100644 --- a/packages/core/parcel-bundler/src/assets/GLSLAsset.js +++ b/packages/core/parcel-bundler/src/assets/GLSLAsset.js @@ -37,7 +37,7 @@ class GLSLAsset extends Asset { } }); - return await promisify(depper.inline.bind(depper))(this.contents, cwd); + return promisify(depper.inline.bind(depper))(this.contents, cwd); } collectDependencies() { diff --git a/packages/core/parcel-bundler/src/assets/HTMLAsset.js b/packages/core/parcel-bundler/src/assets/HTMLAsset.js index e2646c7fbee..2bbdd363458 100644 --- a/packages/core/parcel-bundler/src/assets/HTMLAsset.js +++ b/packages/core/parcel-bundler/src/assets/HTMLAsset.js @@ -168,18 +168,25 @@ class HTMLAsset extends Asset { } for (let attr in node.attrs) { - let elements = ATTRS[attr]; + const attrVal = node.attrs[attr]; + + if (!attrVal) { + continue; + } + // Check for virtual paths - if (node.tag === 'a' && node.attrs[attr].lastIndexOf('.') < 1) { + if (node.tag === 'a' && attrVal.lastIndexOf('.') < 1) { continue; } + let elements = ATTRS[attr]; + if (elements && elements.includes(node.tag)) { let depHandler = this.getAttrDepHandler(attr); let options = OPTIONS[node.tag]; node.attrs[attr] = depHandler.call( this, - node.attrs[attr], + attrVal, options && options[attr] ); this.isAstDirty = true; @@ -204,59 +211,61 @@ class HTMLAsset extends Asset { async generate() { // Extract inline