This is a plugin for webpack.
The main aim is to provide a tool to upload js/css/img/font used in html (or template in other language) to cdn, and then replace the reference with the corresponding cdn url.
node >= 10.5.0
npm i -D webpack-upload-plugin
- This plugin does not provide a service as uploading to cdn. In fact, it actually depends on such service.
- This plugin is for webpack >= 3.
- For webpack@4, set
optimization.minimize
tofalse
! - This plugin doesn't work well with
UglifyJs
plugin! UsebeforeUpload
if you want to compress anyway. - Pay extra attention to your
publicPath
field ofwebpack.config.js
,''
is likely the best choice.
For webpack@2, please use webpack-upload-plugin <= 0.20.0
webpack-upload-plugin
relies on the existence a cdn
object with an upload
method described as below.
type cdnUrl = string
interface cdnRes {
[localPath: string]: cdnUrl
}
// this is what cdn package looks like
interface cdn {
upload: (localPaths: string[]) => Promise<cdnRes>
}
Here is a sudo implementation of cdn
in vanilla javascript.
/**
* @param {string[]} localPaths: list of paths of local files
* @return Promise<cdnRes>: resolved Promise with structure like {localPath: cdnUrl}
*/
function upload(localPaths) {
return Promise.all(
localPaths.map((localPath) => {
return Promise.resolve({
[localPath]: 'cdn_url_for_this_file',
})
})
).then(
(pairs) =>
pairs.reduce((last, pair) => {
return Object.assign(last, pair)
}, {}),
() => ({})
)
}
const cdn = {
upload,
}
For a simple project with such structure:
+-- src
| +-- assets
| | +-- avatar.png
| +-- index.js
| +-- index.css
+-- dist
+-- index.html
+-- webpack.config.js
// in webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const UploadPlugin = require('webpack-upload-plugin')
const cdn = require('xxx-cdn')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '',
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
},
},
],
},
optimization: {
minimize: false, // important! important! important!
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
}),
new UploadPlugin(cdn),
],
}
For webpack v3 users, use
extract-text-webpack-plugin
instead ofmini-css-extract-plugin
Run webpack in build
, then copy all emitted files from build/dist
to project/src
.
Public can only access files from project/public
+-- project
| +-- src
| +-- public
+-- build
| +-- src
| | +-- assets
| | | +-- avatar.png
| | +-- index.js
| | +-- index.css
| +-- dist
| +-- index.html
| +-- webpack.config.js
// only focus on WebpackUploadPlugin here
{
plugins: [
new UploadPlugin(cdn, {
src: path.resolve(__dirname, '..', 'project/src'),
dist: path.resolve(__dirname, '..', 'project/public'),
staticDir: path.resolve(__dirname, '..', 'project/src'),
dirtyCheck: true,
}),
]
}
Make sure
WebpackUploadPlugin
is after any copy-related plugins inplugins
field.
If in
project/public
, there are different prefix frompublicPath
you passed to webpack, then usereplaceFn
to remove such prefix.
const config = {
replaceFn(content, location) {
return path.extname(location) === '.html'
? content.replace(prefix, '')
: content
},
}
If the copy process takes a long time, use
waitFor
to make sure only start uploading when things are settled.
In webpack.config.js
const WebpackUploadPlugin = require('webpack-upload-plugin')
const cdn = require('some-cdn-package')
module.exports = {
plugins: [new WebpackUploadPlugin(cdn, option)],
}
option
is optional.
Valid fields for option
are showed below:
If switch on, we assume that all files needed is in the output.path
of your webpack config file. No more src
, dist
, staticDir
are needed (and will be ignored if being set).
This is an opinionated setup. Under the hood,
src
,dist
andstaticDir
are equivalently set to the same asoutput.path
The reason for this field is that very likely, files that need to be uploaded are not all generated by webpack bundling but something like copying. In such cases, those copied files cannot be detected through the webpack config, and hence won't be uploaded automatically. With this feature, your life would be easier.
try
smartAssMode
first
Where your valid raw template files would appear (with reference to local js/css files). Default to be where html files would be emitted to based on your webpack configuration.
For templates not dynamically emitted by webpack, eg, files like php
, tpl
, phtml
and so on that are managed by some server logic not client one, this field could be used.
Use absolute path
try
smartAssMode
first
Where to emit final template files. Only use this when there is a need to separate origin outputs with cdn ones. Default to be same as src
.
Think in this way:
template from src -> template with cdn reference -> template to dist
Use dist
only if the third step is needed.
Use absolute path
Adjust cdn url accordingly. Cdn url would be passed in, and you need to return a string.
const url = 'http://domain.com/cdn/bundle.js'
const urlCb = (input) => input.replace(/^https?/, 'https')
Type of templates needed to match. In case you have a project with php, smarty, or other template language instead of html. Default to ['html']
Again, for projects using server language template, like php
, you could set resolve
to ['php', 'phtml']
try
smartAssMode
first
If static files (js/css/images etc) emitted by webpack is not what you want, or not enough(normally happens when you need to copy all resources emitted by webpack to another directory), then set staticDir
to the directory that contains all your desired resource files (js/css/images etc).
Use absolute path
Compression can be done here. Two arguments are fileContent
and fileLocation
(with extension name of course). You need to return the compression result as string.
// if you want to compress js before upload
const UglifyJs = require('uglify-js')
const path = require('path')
const beforeUpload = (content, location) => {
if (path.extname(location) === '.js') {
return UglifyJs.minify(content).code
}
return content
}
For some complex (ancient) projects, you may have multiple publicPath
or corresponding concepts. To handle such cases accordingly, you can pass a replaceFn
function, which will receive two parameters, which are fileContent
and location
in that order. fileContent
would be file in string format with local resources reference. location
is the location of fileContent
on your file system. This function will be called when plugin start to replace local reference. The string replaceFn
returns will represent the desired publicPath
, which will be used as the input template to replace all local reference with cdn ones.
// in your latest webpack.config.js
module.exports = {
output {
publicPath: 'public/static'
}
}
<!-- in an ancient template file -->
<!-- bundle.js is actually emitted by webpack -->
<!-- but some copy action involved in later sometime -->
<!-- public/static/bundle.js -> public/js/bundle.js -->
<!-- but this src attribute cannot be changed due to some weird reason -->
<script src="public/js/bundle.js"></script>
In such case, staticDir
could be useful. Or you can use replaceFn
as followed:
const replaceFn = (content, location) => {
const oldPublicReg = /src="public\/js/
if (oldPublicReg.test(content)) {
return content.replace(oldPublicReg, `src="public/static"`)
}
}
In this way, public/static/bundle.js
will be uploaded before it get moved to public/js
, and the reference replacement stay accurate.
A function that returns a Promise. The plugin will wait for the Promise to resolve and then start everything.
// things won't start till 1000ms later
const waitFor = new Promise((resolve) => {
setTimeout(resolve, 1000)
})
For cases where chunk file can also be entry file, set dirtyCheck
to true
to make sure entry file would be updated properly.
Called when everything finished. You can further play with files here.
const onFinish = () => {
console.log('just want to print this')
}
Called when encounter any error.
Whether to print all uploading file names during the process
Extra config to pass to cdn.upload
method. Something Like cdn.upload(location, passToCdn)
.
// if your original cdn package has API like this
const cdn = require('some-cdn-package')
const passToCdn = {
https: true,
}
cdn.upload(files, passToCdn)
Enable cache to speed up. Default to false
.
Directory to emit the upload cache file. Use this when you want to manage the cache file by any VCS.
const path = require('path')
const cacheLocation = path.resolve(__dirname, 'cacheDirectory')
Uploading files is not done by once. By using sliceLimit
, you can limit the number of files being uploaded at once.
Force to copy generated templates from src
to dist
even if no cdn Url has been matched. Default to true
.
When set to
false
, the generated templates will still be copied fromsrc
todist
as long as if no corresponding file exists indist
.
Try to handle async CSS files emitted by mini-css-extract-plugin
.
Configure when to run the plugin. See here
Viola! That's all : )
Copyright (c) 2017-present, Yuchen Liu