Skip to content
This repository has been archived by the owner on Apr 5, 2021. It is now read-only.

v3.3.5

Compare
Choose a tag to compare
@mmiller42 mmiller42 released this 16 Sep 01:40
· 51 commits to master since this release

What's New in v3.3.5

This is a summary of the differences between v3.3.5 and v3.3.4.

Commits

Show commits
SHA Author Message
438ae30 mmiller42 CircleCI setup
67a24b4 mmiller42 CircleCI setup
6b159ec mmiller42 Naming steps
73386cd mmiller42 Trying to fix semver bug?
c934e78 mmiller42 Trying to fix semver bug?
02dd50a mmiller42 sudoit
35af31d mmiller42 build step
0d68b81 mmiller42 testing tests!
bc01e19 mmiller42 Unit test coverage! And some bugs I fixed along the way.
1906e18 mmiller42 Add publish configuration and CI badge
2321037 mmiller42 3.3.5
07fa240 mmiller42 Publish based on commit message instead of tag, since tags aren't supported in CircleCI 2.0
6a59200 mmiller42 reversing version manually
a787f60 mmiller42 3.3.5

Changed files

.circleci/config.yml

Show changes
@@ -0,0 +1,41 @@
+version: 2
+jobs:
+  build:
+    docker:
+      - image: circleci/node:latest
+    working_directory: ~/html-webpack-externals-plugin
+
+    steps:
+      - checkout
+      - run:
+          name: Authenticate to npm registry
+          command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
+      - run:
+          name: Update npm
+          command: |
+            npm install npm@latest --no-save &&
+            sudo rm -rf /usr/local/lib/node_modules/npm &&
+            sudo mv node_modules/npm /usr/local/lib/node_modules/npm
+      - restore_cache:
+          key: dependency-cache-{{ checksum "package.json" }}
+      - run:
+          name: Install dependencies
+          command: npm install
+      - save_cache:
+          key: dependency-cache-{{ checksum "package.json" }}
+          paths:
+            - node_modules
+      - run:
+          name: Build
+          command: npm run build
+      - run:
+          name: Test
+          command: npm test
+      - deploy:
+          name: Publish
+          command: |
+            if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; then
+              npm publish
+            else
+              echo "No tag pushed, skipping deploy"
+            fi

.gitignore

Show changes
@@ -6,11 +6,14 @@
 
 # Dependencies
 node_modules
-
+˚
 # Logs
 *.log
 .node_repl_history
 .node_history
 
 # Build files
 lib
+
+# Temporary files
+tmp

.npmignore

Show changes
@@ -11,3 +11,6 @@ node_modules
 *.log
 .node_repl_history
 .node_history
+
+# Temporary files
+tmp

README.md

Show changes
@@ -1,4 +1,4 @@
-# html-webpack-externals-plugin
+# html-webpack-externals-plugin [![CircleCI](https://circleci.com/gh/mmiller42/html-webpack-externals-plugin.svg?style=svg)](https://circleci.com/gh/mmiller42/html-webpack-externals-plugin)
 
 Webpack plugin that works alongside [\`html-webpack-plugin\`](https://github.com/jantimon/html-webpack-plugin) to use pre-packaged vendor bundles.
 
@@ -251,7 +251,7 @@ You should include a trailing slash in your public path, and a leading slash if
 This example assumes \`bootstrap\` is installed in the app. It:
 
 1. copies \`node_modules/bootstrap/dist/css/bootstrap.min.css\` to \`<output path>/vendor/bootstrap/dist/css/bootstrap.min.css\`
-1. adds \`<link href="/public/vendor/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">\` to your HTML file, before your chunks
+1. adds \`<link href="/assets/vendor/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">\` to your HTML file, before your chunks
 
 \`\`\`js
 new HtmlWebpackExternalsPlugin({

package-lock.json

Inline diff not displayed. View the whole file

package.json

Show changes
@@ -1,6 +1,6 @@
 {
   "name": "html-webpack-externals-plugin",
-  "version": "3.3.4",
+  "version": "3.3.5",
   "description": "Webpack plugin that works alongside html-webpack-plugin to use pre-packaged vendor bundles.",
   "keywords": [
     "htmlwebpackplugin",
@@ -12,12 +12,13 @@
   "scripts": {
     "prepack": "npm run build",
     "precommit": "lint-staged",
+    "test": "mocha --require babel-register",
     "build": "rm -rf lib && babel src --out-dir lib --source-maps --copy-files",
     "watch": "npm run build -- --watch"
   },
   "lint-staged": {
     "*.js": [
-      "prettier --write --no-semi --single-quote --trailing-comma es5 '{src/**/*.{js,json},*.json}'",
+      "prettier --write --no-semi --single-quote --trailing-comma es5 '{src/**/*.{js,json},test/**/*.js,*.json}'",
       "git add"
     ]
   },
@@ -41,9 +42,19 @@
     "babel-plugin-transform-class-properties": "^6.24.1",
     "babel-plugin-transform-object-rest-spread": "^6.23.0",
     "babel-preset-env": "^1.6.0",
+    "babel-register": "^6.24.1",
+    "bootstrap": "^3.3.7",
+    "css-loader": "^0.28.4",
+    "escape-string-regexp": "^1.0.5",
+    "extract-text-webpack-plugin": "^3.0.0",
+    "html-webpack-plugin": "^2.0.0",
     "husky": "^0.14.3",
-    "lint-staged": "^4.0.1",
-    "prettier": "^1.5.3"
+    "jquery": "^3.2.1",
+    "lint-staged": "^4.0.2",
+    "mocha": "^3.4.2",
+    "prettier": "^1.5.3",
+    "rimraf": "^2.6.1",
+    "webpack": "^3.3.0"
   },
   "peerDependencies": {
     "html-webpack-plugin": "^2.0.0"

src/HtmlWebpackExternalsPlugin.js

Show changes
@@ -74,10 +74,15 @@ export default class HtmlWebpackExternalsPlugin {
       }
     }
 
-    const publicPath =
-      this.publicPath == null
-        ? compiler.options.output.publicPath
-        : this.publicPath
+    const publicPath = (() => {
+      if (this.publicPath != null) {
+        return this.publicPath
+      } else if (compiler.options.output.publicPath != null) {
+        return compiler.options.output.publicPath
+      } else {
+        return ''
+      }
+    })()
 
     const pluginsToApply = []
 

test/HtmlWebpackExternalsPlugin.spec.js

Show changes
@@ -0,0 +1,298 @@
+import assert from 'assert'
+import HtmlWebpackPlugin from 'html-webpack-plugin'
+import HtmlWebpackExternalsPlugin from '../lib/'
+import {
+  cleanUp,
+  runWebpack,
+  checkBundleExcludes,
+  checkCopied,
+  checkHtmlIncludes,
+} from './utils'
+
+describe('HtmlWebpackExternalsPlugin', function() {
+  afterEach(cleanUp)
+
+  it('validates the arguments passed to the constructor', function() {
+    assert.throws(
+      () => new HtmlWebpackExternalsPlugin({}),
+      /should have required property 'externals'/
+    )
+  })
+
+  it('Local JS external example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'jquery',
+            entry: 'dist/jquery.min.js',
+            global: 'jQuery',
+          },
+        ],
+      })
+    )
+      .then(() => checkBundleExcludes('jQuery'))
+      .then(() => checkCopied('vendor/jquery/dist/jquery.min.js'))
+      .then(() => checkHtmlIncludes('vendor/jquery/dist/jquery.min.js', 'js'))
+  })
+
+  it('Local CSS external example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+          },
+        ],
+      })
+    )
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkHtmlIncludes('vendor/bootstrap/dist/css/bootstrap.min.css', 'css')
+      )
+  })
+
+  it('Local external with supplemental assets example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+            supplements: ['dist/fonts/'],
+          },
+        ],
+      })
+    )
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkCopied(
+          'vendor/bootstrap/dist/fonts/glyphicons-halflings-regular.eot'
+        )
+      )
+      .then(() =>
+        checkHtmlIncludes('vendor/bootstrap/dist/css/bootstrap.min.css', 'css')
+      )
+  })
+
+  it('CDN example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'jquery',
+            entry: 'https://unpkg.com/[email protected]/dist/jquery.min.js',
+            global: 'jQuery',
+          },
+        ],
+      })
+    )
+      .then(() => checkBundleExcludes('jQuery'))
+      .then(() =>
+        checkHtmlIncludes(
+          'https://unpkg.com/[email protected]/dist/jquery.min.js',
+          'js'
+        )
+      )
+  })
+
+  it('URL without implicit extension example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'google-roboto',
+            entry: {
+              path: 'https://fonts.googleapis.com/css?family=Roboto',
+              type: 'css',
+            },
+          },
+        ],
+      })
+    ).then(() =>
+      checkHtmlIncludes('https://fonts.googleapis.com/css?family=Roboto', 'css')
+    )
+  })
+
+  it('Module with multiple entry points example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: [
+              'dist/css/bootstrap.min.css',
+              'dist/css/bootstrap-theme.min.css',
+            ],
+            supplements: ['dist/fonts/'],
+          },
+        ],
+      })
+    )
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkCopied('vendor/bootstrap/dist/css/bootstrap-theme.min.css')
+      )
+      .then(() =>
+        checkCopied(
+          'vendor/bootstrap/dist/fonts/glyphicons-halflings-regular.eot'
+        )
+      )
+      .then(() =>
+        checkHtmlIncludes('vendor/bootstrap/dist/css/bootstrap.min.css', 'css')
+      )
+      .then(() =>
+        checkHtmlIncludes(
+          'vendor/bootstrap/dist/css/bootstrap-theme.min.css',
+          'css'
+        )
+      )
+  })
+
+  it('Appended assets example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+            append: true,
+          },
+        ],
+      })
+    )
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkHtmlIncludes(
+          'vendor/bootstrap/dist/css/bootstrap.min.css',
+          'css',
+          true
+        )
+      )
+  })
+
+  it('Cache-busting with hashes example', function() {
+    let hash
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+          },
+        ],
+        hash: true,
+      })
+    )
+      .then(stats => {
+        hash = stats.toJson().hash
+      })
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkHtmlIncludes(
+          \`vendor/bootstrap/dist/css/bootstrap.min.css?${hash}\`,
+          'css'
+        )
+      )
+  })
+
+  it('Customizing output path example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+          },
+        ],
+        outputPath: 'thirdparty',
+      })
+    )
+      .then(() =>
+        checkCopied('thirdparty/bootstrap/dist/css/bootstrap.min.css')
+      )
+      .then(() =>
+        checkHtmlIncludes(
+          'thirdparty/bootstrap/dist/css/bootstrap.min.css',
+          'css'
+        )
+      )
+  })
+
+  it('Customizing public path example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin(),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+          },
+        ],
+        publicPath: '/assets/',
+      })
+    )
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkHtmlIncludes(
+          '/assets/vendor/bootstrap/dist/css/bootstrap.min.css',
+          'css'
+        )
+      )
+  })
+
+  it('Specifying which HTML files to affect example', function() {
+    return runWebpack(
+      new HtmlWebpackPlugin({
+        filename: 'index.html',
+      }),
+      new HtmlWebpackPlugin({
+        filename: 'about.html',
+      }),
+      new HtmlWebpackExternalsPlugin({
+        externals: [
+          {
+            module: 'bootstrap',
+            entry: 'dist/css/bootstrap.min.css',
+          },
+        ],
+        files: ['about.html'],
+      })
+    )
+      .then(() => checkCopied('vendor/bootstrap/dist/css/bootstrap.min.css'))
+      .then(() =>
+        checkHtmlIncludes(
+          'vendor/bootstrap/dist/css/bootstrap.min.css',
+          'css',
+          false,
+          'about.html'
+        )
+      )
+      .then(() => {
+        return new Promise((resolve, reject) => {
+          checkHtmlIncludes(
+            'vendor/bootstrap/dist/css/bootstrap.min.css',
+            'css',
+            false,
+            'index.html'
+          )
+            .then(() =>
+              reject(
+                'index.html should not have had the assets inserted into the HTML'
+              )
+            )
+            .catch(() => resolve())
+        })
+      })
+  })
+})

test/fixtures/app.js

Show changes
@@ -0,0 +1,3 @@
+const $ = require('jquery')
+
+$('body').css('background', 'red')

test/fixtures/style.css

Show changes
@@ -0,0 +1,3 @@
+body {
+	color: blue;
+}

test/utils/index.js

Show changes
@@ -0,0 +1,126 @@
+import path from 'path'
+import fs from 'fs'
+import assert from 'assert'
+import webpack from 'webpack'
+import ExtractTextPlugin from 'extract-text-webpack-plugin'
+import escapeRegExp from 'escape-string-regexp'
+import rimraf from 'rimraf'
+
+const OUTPUT_PATH = path.resolve(__dirname, '..', 'tmp')
+
+export function cleanUp() {
+  return promisify(rimraf, OUTPUT_PATH)
+}
+
+export function runWebpack(...plugins) {
+  return promisify(webpack, {
+    entry: {
+      app: path.resolve(__dirname, '..', 'fixtures', 'app.js'),
+      style: path.resolve(__dirname, '..', 'fixtures', 'style.css'),
+    },
+    output: {
+      path: OUTPUT_PATH,
+      filename: '[name].js',
+    },
+    module: {
+      loaders: [
+        {
+          test: /\.css$/,
+          loader: ExtractTextPlugin.extract({ use: 'css-loader' }),
+        },
+      ],
+    },
+    plugins: [new ExtractTextPlugin({ filename: '[name].css' }), ...plugins],
+  }).then(stats => {
+    assert.strictEqual(
+      stats.hasErrors(),
+      false,
+      stats.toJson().errors.toString()
+    )
+    return stats
+  })
+}
+
+export function checkBundleExcludes(external) {
+  return promisify(
+    fs.readFile,
+    path.join(OUTPUT_PATH, 'app.js'),
+    'utf8'
+  ).then(contents => {
+    assert.ok(
+      contents.indexOf(\`module.exports = ${external}\`) > -1,
+      \`${external} was not excluded from the bundle\`
+    )
+  })
+}
+
+export function checkCopied(file) {
+  return promisify(fs.access, path.join(OUTPUT_PATH, file))
+}
+
+export function checkHtmlIncludes(
+  file,
+  type,
+  append = false,
+  htmlFile = 'index.html'
+) {
+  return promisify(
+    fs.readFile,
+    path.join(OUTPUT_PATH, htmlFile),
+    'utf8'
+  ).then(contents => {
+    if (type === 'js') {
+      assert.ok(
+        contents.match(new RegExp(\`<script.*src="${escapeRegExp(file)}".*>\`)),
+        \`${file} script was not inserted into the HTML output\`
+      )
+    } else if (type === 'css') {
+      assert.ok(
+        contents.match(new RegExp(\`<link.*href="${escapeRegExp(file)}".*>\`)),
+        \`${file} link was not inserted into the HTML output\`
+      )
+    }
+
+    assert.ok(
+      inequal(
+        append ? '<' : '>',
+        contents.indexOf(type === 'js' ? 'app.js' : 'style.css'),
+        contents.indexOf(file)
+      ),
+      \`${file} should have been inserted ${append
+        ? 'after'
+        : 'before'} the bundle\`
+    )
+  })
+}
+
+export function promisify(fn, ...args) {
+  return new Promise((resolve, reject) => {
+    fn(...args, (err, result) => {
+      if (err) {
+        reject(err)
+        return
+      }
+      resolve(result)
+    })
+  })
+}
+
+function inequal(operator, a, b) {
+  switch (operator) {
+    case '<':
+      return a < b
+    case '>':
+      return a > b
+    case '<=':
+      return a <= b
+    case '>=':
+      return a >= b
+    case '!=':
+      return a != b
+    case '!==':
+      return a !== b
+    default:
+      throw new Error(\`Unknown operator ${operator}\`)
+  }
+}