UNPKG

@vip30/webpack-aliyun-oss

Version:

a webpack(version>=4) plugin to upload assets to aliyun oss, can be used with or without webpack.

328 lines (278 loc) 8.05 kB
const fs = require('fs'); const path = require('path'); const OSS = require('ali-oss'); const globby = require("globby"); const Listr = require('listr'); require('colors'); class WebpackAliyunOss { constructor(options) { const { region, accessKeyId, accessKeySecret, bucket, ossOptions = {} } = options; this.config = Object.assign({ test: false, // 测试 dist: '', // oss目录 buildRoot: '.', // 构建目录名 deleteOrigin: false, // 是否删除源文件 timeout: 30 * 1000, // 超时时间 parallel: 5, // 并发数 setOssPath: null, // 手动设置每个文件的上传路径 setHeaders: null, // 设置头部 overwrite: false, // 覆盖oss同名文件 bail: false, // 出错中断上传 logToLocal: false // 出错信息写入本地文件 }, options); this.configErrStr = this.checkOptions(options); this.client = new OSS({ region, accessKeyId, accessKeySecret, bucket, ...ossOptions }); this.filesUploaded = [] this.filesIgnored = [] } apply(compiler) { if (compiler) { return this.doWithWebpack(compiler); } else { return this.doWidthoutWebpack(); } } doWithWebpack(compiler) { compiler.hooks.afterEmit.tapPromise('WebpackAliyunOss', async (compilation) => { if (this.configErrStr) { compilation.errors.push(this.configErrStr); return Promise.resolve(); } const outputPath = path.resolve(this.slash(compiler.options.output.path)); const { from = outputPath + '/**' } = this.config; const files = await globby(from, {dot: true}); if (files.length) { try { await this.upload(files, true, outputPath); console.log(''); console.log(' All files uploaded successfully '.bgGreen.bold.white); } catch (err) { compilation.errors.push(err); return Promise.reject(err); } } else { console.log('no files to be uploaded'); return Promise.resolve('no files to be uploaded'); } }); } async doWidthoutWebpack() { if (this.configErrStr) return Promise.reject(this.configErrStr); const { from } = this.config; const files = await globby(from); if (files.length) { try { await this.upload(files); console.log(''); console.log(' All files uploaded successfully '.bgGreen.bold.white); } catch (err) { return Promise.reject(err); } } else { console.log('no files to be uploaded'); return Promise.resolve('no files to be uploaded'); } } async upload(files, inWebpack, outputPath = '') { const { dist, setHeaders, deleteOrigin, setOssPath, timeout, test, overwrite, bail, parallel, logToLocal } = this.config; if (test) { console.log(''); console.log('Currently running in test mode. your files won\'t realy be uploaded.'.green.underline); console.log(''); } else { console.log(''); console.log('Your files will be uploaded very soon.'.green.underline); console.log(''); } files = files.map(file => ({ path: file, fullPath: path.resolve(file) })) this.filesUploaded = [] this.filesIgnored = [] this.filesErrors = [] const basePath = this.getBasePath(inWebpack, outputPath) const _upload = async file => { const { fullPath: filePath, path: fPath } = file let ossFilePath = this.slash( path.join( dist, ( setOssPath && setOssPath(filePath) || basePath && filePath.split(basePath)[1] || '' ) ) ); if (test) { return Promise.resolve(fPath.blue.underline + ' is ready to upload to ' + ossFilePath.green.underline); } if (!overwrite) { const fileExists = await this.fileExists(ossFilePath) if (fileExists) { this.filesIgnored.push(filePath) return Promise.resolve(fPath.blue.underline + ' ready exists in oss, ignored'); } } const headers = setHeaders && setHeaders(filePath) || {} let result try { result = await this.client.put(ossFilePath, filePath, { timeout, // headers: !overwrite ? Object.assign(headers, { 'x-oss-forbid-overwrite': true }) : headers headers }) } catch (err) { // if (err.name === 'FileAlreadyExistsError') { // this.filesIgnored.push(filePath) // return Promise.resolve(fPath.blue.underline + ' ready exists in oss, ignored'); // } this.filesErrors.push({ file: fPath, err: { code: err.code, message: err.message, name: err.name } }); const errorMsg = `Failed to upload ${fPath.underline}: ` + `${err.name}-${err.code}: ${err.message}`.red; return Promise.reject(new Error(errorMsg)) } result.url = this.normalize(result.url); this.filesUploaded.push(fPath) if (deleteOrigin) { fs.unlinkSync(filePath); this.deleteEmptyDir(filePath); } return Promise.resolve(fPath.blue.underline + ' successfully uploaded, oss url => ' + result.url.green) } let len = parallel const addTask = () => { if (len < files.length) { tasks.add(createTask(files[len])) len++ } } const createTask = file => ({ title: `uploading ${file.path.underline}`, task(_, task) { return _upload(file) .then(msg => { task.title = msg; addTask() }) .catch(e => { if (!bail) addTask() return Promise.reject(e) }) } }); const tasks = new Listr( files.slice(0, len).map(createTask), { exitOnError: bail, concurrent: parallel }) await tasks.run().catch(() => { }); // this.filesIgnored.length && console.log('files ignored due to not overwrite'.blue, this.filesIgnored); if (this.filesErrors.length) { console.log(' UPLOAD ENDED WITH ERRORS '.bgRed.white, '\n'); logToLocal && fs.writeFileSync(path.resolve('upload.error.log'), JSON.stringify(this.filesErrors, null, 2)) return Promise.reject(' UPLOAD ENDED WITH ERRORS ') } } getBasePath(inWebpack, outputPath) { if (this.config.setOssPath) return ''; let basePath = '' if (inWebpack) { if (path.isAbsolute(outputPath)) basePath = outputPath else basePath = path.resolve(outputPath) } else { const { buildRoot } = this.config if (path.isAbsolute(buildRoot)) basePath = buildRoot else basePath = path.resolve(buildRoot) } return this.slash(basePath) } fileExists(filepath) { // return this.client.get(filepath) return this.client.head(filepath) .then((result) => { return result.res.status == 200 }).catch((e) => { if (e.code == 'NoSuchKey') return false }) } normalize(url) { const tmpArr = url.split(/\/{2,}/); if (tmpArr.length >= 2) { const [protocol, ...rest] = tmpArr; url = protocol + '//' + rest.join('/'); } return url; } slash(path) { const isExtendedLengthPath = /^\\\\\?\\/.test(path); // const hasNonAscii = /[^\u0000-\u0080]+/.test(path); if (isExtendedLengthPath) { return path; } return path.replace(/\\/g, '/'); } deleteEmptyDir(filePath) { let dirname = path.dirname(filePath); if (fs.existsSync(dirname) && fs.statSync(dirname).isDirectory()) { fs.readdir(dirname, (err, files) => { if (err) console.error(err); else { if (!files.length) fs.rmdir(dirname, () => { }) } }) } } checkOptions(options = {}) { let { from, region, accessKeyId, accessKeySecret, bucket, ossOptions = {} } = options; let errStr = ''; if (!region && !ossOptions.region) errStr += '\nregion not specified'; if (!accessKeyId && !ossOptions.accessKeyId) errStr += '\naccessKeyId not specified'; if (!accessKeySecret && !ossOptions.accessKeySecret) errStr += '\naccessKeySecret not specified'; if (!bucket && !ossOptions.bucket) errStr += '\nbucket not specified'; if (Array.isArray(from)) { if (from.some(g => typeof g !== 'string')) errStr += '\neach item in from should be a glob string'; } else { let fromType = typeof from; if (['undefined', 'string'].indexOf(fromType) === -1) errStr += '\nfrom should be string or array'; } return errStr; } } module.exports = WebpackAliyunOss;