前言
咱们先思考一个问题:如果不应用webpack,前端可能开发我的项目吗?
先别着急说出答案,咱们持续往下看...
工作中,咱们都是应用框架的脚手架进行开发,支流的趋势...
vue-cli & create-react-app和webpack的关系
咱们晓得,无论是Vue的vue-cli还是React的create-react-app这样的脚手架,实际上都是给webpack做了一层封装,包了一层壳子,并预设了一些默认罕用的配置项(当然还有别的货色),以便晋升开发效率。
所以它们的关系是:脚手架只是给webpack穿上了一个马甲...
不过有时候脚手架提供的默认配置项不够用了,就须要咱们手动去写一些plugin或者loader从而实现咱们想要的性能。
学习本文的播种
- 通俗易懂地回顾webpack知识点
- 学习在vue-cli脚手架中写webpack的plugin的知识点
- 学习在vue-cli脚手架中写webpack的loader的知识点
webpack
平时咱们写代码都是模块化、组件化(工程化)
进行开发,导致代码会进行拆分、细化、公共局部提取、援用等...
为何要模块化、组件化(工程化)
进行开发呢?因为:这是软件开发的支流和趋势...
什么是webpack & 谈谈你对webpack的了解
- 平时咱们写代码都是
模块化、组件化(工程化)
进行开发,导致代码会进行拆分、细化、公共局部提取、援用等... - 比方:咱们会写很多.vue文件(当然还有别的文件如.less等)。然而咱们写的代码是最终要被浏览器拜访解析执行的,而浏览器不意识.vue文件,也不意识.less文件!
- 浏览器不意识就不能解析,不能用。
- 浏览器倒是意识js、css、html,能够解析出相应内容,并渲染展现。
- 又因为 .vue文件和.less文件实际上也只是html、css、js
化妆之后
的款式。 - 那这样,搞一个工具,可能让.vue文件和.less文件
卸妆
成html、css、js就行了。 - 而webpack恰好可能做到这一点(编译转化打包)
所以,webpack就是:一个转化编译打包工具,将一些浏览器不意识的花里胡哨的文件转化为浏览器意识的html、css、js文件。
还记得咱们最终打包好的dist文件夹目录吗?外面就只有:
html、css、js等
一些资源...这样形容,不是非常谨严。精确来说,webpack是一个动态模块资源打包工具,官网:https://webpack.docschina.org/concepts/
回到最开始的那个问题~
如果不应用webpack,前端可能开发我的项目吗?
- 问:如果不应用webpack,前端可能开发我的项目吗?
- 答:如果一个我的项目炒鸡小,只是用来展现一点点货色,齐全能够应用原生的html、css、js去写,这样的话,就用不到
咱们要晓得webpack的作用就是,去转化编译打包脚手架、工程化的大我的项目。如果是一个小我的项目,齐全不须要去用工程化的形式开发,间接写,写完了丢到服务器上,间接用
前端工程化 == 模块化 + 组件化 + 自动化 + ...
webpack的几个重要概念
- 打包入口(entry)
- 打包输入(output)
- 加载器(loader)
- 插件(plugin)
- 模式(mode)
- nodejs环境(environment)
webpack打包入口-entry
- 咱们晓得,咱们开发我的项目有很多文件,比方a、b、c等。a援用了b中的货色,而b又援用了c中的货色。那么打包翻译的话,就须要指定从哪个文件开始打包,打包的过程中如果遇到了有别的援用,就顺藤摸瓜...
- webpack中默认打包的入口文件是
./src/index.js
文件,这个文件中援用了好多别的文件,所以webpack持续顺藤摸瓜寻找、编译、转化、打包。 - 而vue-cli脚手架中的入口文件是src目录下的main.js文件(vue-cli改写了webpack默认的入口文件地位罢了)
- 这里咱们能够去vue-cli仓库代码中去找到相干的代码,能看到指定的打包入口
vue-cli仓库地址:https://github.com/vuejs/vue-cli
大家把代码下载下来,Code --> Download ZIP ,而后在vscode中关上代码,在左上角第二个放大镜中搜寻关键字:src/main.js
有很多的关键词,其中有一个get entryFile
办法,代码如下:
/*** Get the entry file taking into account typescript.** @readonly*/get entryFile () { if (this._entryFile) return this._entryFile return (this._entryFile = fs.existsSync(this.resolve('src/main.ts')) ? 'src/main.ts' : 'src/main.js')}
对应截图如下:
其实vue-cli中有很多的中央的代码,都告知了咱们vue-cli是将main.js作为webpack打包的入口的,大家多看看...
好了,至此咱们见证了webpack的打包入口(entry)在vue-cli脚手架中的具体利用展示模式。大家也能够在create-react-app中去看一下webpack的打包入口文件,一个意思
vue-cli
和webpack
的关系,换句话说,也像苹果手机
和富士康
的关系...
webpack打包输入-output
咱们晓得,平时我的项目写完,公布上线,打包输入的个别都是一个dist文件夹(也能够改)
原始的webpack是这样写:
const path = require('path');module.exports = { entry: './src/index.js', // 从以后同级目录下的index.js作为入口文件,顺藤摸瓜开始打包 output: { path: path.resolve(__dirname, 'dist'), // 这里的path值要是一个绝对路径,如:E:\echarts\code\dist,所以要应用path模块来操作 filename: 'myDemo.js', },};
vue-cli中叫做outputDir并指定了默认值为dist(实际上就是webpack中的output,又是套壳子),咱们通过在vue.config.js文件中更改outputDir的值,即可批改打包换名字了
vue-cli中的代码如下:
exports.defaults = () => ({ // project deployment base publicPath: '/', // where to output built files outputDir: 'dist', // 即为webpack中的output // where to put static assets (js/css/img/font/...) assetsDir: '', // filename for index.html (relative to outputDir) indexPath: 'index.html', // ...... devServer: { /* open: process.platform === 'darwin', host: '0.0.0.0', port: 8080, https: false, hotOnly: false, proxy: null, // string | Object before: app => {} */ }})
留神看,上述的参数,就是vue.config.js须要咱们设定的一些参数
vue-cli中的webpack工作流程
- 咱们在vue.config.js中写的合乎vue-cli语法的代码,会被传递到vue-cli代码中
- vue-cli接管到当前,会再转化一下,转化成为合乎webpack语法的配置
- 并通过webpack-merge这个插件,传递给webpack中。
- webpack拿到对应配置项当前,再执行相应的打包策略形式
create-react-app这个脚手架也是相似,大抵都是套壳子,定规定,拿参数(解决),丢给webpack去打包
模式(mode)
- 开发模式(development)
- 生产模式(production)
nodejs环境(environment)
咱们晓得webpack是用js语言写的,在nodejs创立的环境中运行,咱们能够通过我的项目中的node_modules/webpack/bin/webpack.js文件看到 如下图,看一下:
child_process为node的子过程
高深莫测...
webpack工作流程
在nodejs环境下,从入口递归寻找各个模块(组件)依赖关系,去打包,遇到不能间接辨认的比方.vue文件、.less文件,就应用对应的loader去解析它。另外如果还能够在webpack的生命周期钩子的某一个工夫节点,去操作打包的内容,从而管制打包的后果。
vue.config配置webpack插件的办法,对象写法或函数写法
实际上,学习webpack学的就是,别的开发者或者公司去开发的loader或者plugin,学的是webpack的生态。
webpack加载器-loader
什么是loader
loader顾名思义,就是加载的意思,加载什么呢?加载webpack不能间接意识的文件,加载好当前,以供webpack去打包。
webpack间接意识的只有js和json文件内容
有哪些常见的loader
- vue-loader去加载.vue文件
- react-loader用于加载react组件的
- style-loader将css款式挂到style标签下
- css-loader编译css款式文件
- less-loader去加载.less款式文件
- postcss-loader给款式加上浏览器前缀
- file-loader和url-loader能够压缩图片资源(后者可压成base64)
- babel-loader、ts-loader、eslint-loader等
loader执行程序
从下到上,从右到左。
简略的loader之去除console.log
第一步,src目录下新建文件夹myLoader,内创立js文件removeConsole.js文件
一个loader就是一个模块化的js文件
第二步,裸露一个函数,loader实质上是一个函数,在函数体外部,能够去对代码(字符串)进行加工
plugin也是相似,也能够对代码字符串进行加工,不过性能更加弱小
第三步,写loader函数逻辑内容
const reg = /(console.log\()(.*)(\))/g;module.exports = function (source) { console.log('source', source); source = source.replace(reg, "") return source;}
loader就是一个加工函数,回想起js中的经典的一句话,万物皆可函数
第四步,在vue.config.js中的configureWebpack中增加应用本人写的loader
/*** 增加本人写的模块loader* */module: { rules: [ /** * 对.vue和.js文件失效,不蕴含node_modules大文件夹,加载器的地位在 * 当前目录下的./src/myLoader/removeConsole.js * */ // { // test: /\.vue$/, // exclude: /node_modules/, // loader: path.resolve(__dirname, "./src/myLoader/removeConsole.js") // }, // { // test: /\.js$/, // exclude: /node_modules/, // loader: path.resolve(__dirname, "./src/myLoader/removeConsole.js") // } ],}
如果想要给loader传参,接参,能够在loader函数外部应用this.query接管,或者npm i -D loader-utils工具包去进一步操作。
残缺代码示例,见GitHub仓库:https://github.com/shuirongshuifu/elementSrcCodeStudy
更多开发loader细节,见官网文档:
webpack插件-plugin
什么是plugin
- loader解决不了的,去应用plugin去解决
- webpack在打包资源代码文件时,也会有先后执行步骤,换句话说即为webpack的生命周期
- 所以咱们能够在对应生命周期的钩子函数中,去编写一些代码从而影响最终的打包后果
- 这些编写的代码,即为webpack的插件(代码)
webpack的打包也会有很多生命周期,plugin就是在适合的机会,通过webpack提供的api,去扭转输入后果。留神,loader是一个转换器,运行在打包之前,而plugin在整个编译周期都起作用。
plugin构造
- plugin是一个class类(构造函数)
- 类中的constructor函数用来接参
- apply函数的compiler对象自带很多的api能够供咱们调用
- 通过这些api的应用最终影响打包后果
如下代码:
class myPlugin { constructor(options) { console.log("接参-->", options); } apply(compiler) { console.log('下面有十分多api,可参见compiler打印后果', compiler) }}module.exports = myPlugin
打印的compiler对象
通过下方的打印后果,咱们确实能够看到compiler.hooks.xxx有很多暴露出的api
实际上去编写webpack中的plugin就是,去正当应用Compiler的相干hooks
Compiler { _pluginCompat: SyncBailHook { _args: [ 'options' ], taps: [ [Object], [Object], [Object] ], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, // 钩子函数,即为生命周期 hooks: { shouldEmit: SyncBailHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, done: AsyncSeriesHook { _args: [Array], taps: [Array], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, additionalPass: AsyncSeriesHook { _args: [], taps: [Array], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, beforeRun: AsyncSeriesHook { _args: [Array], taps: [Array], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, run: AsyncSeriesHook { _args: [Array], taps: [], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, emit: AsyncSeriesHook { _args: [Array], taps: [Array], interceptors: [Array], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, assetEmitted: AsyncSeriesHook { _args: [Array], taps: [], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, afterEmit: AsyncSeriesHook { _args: [Array], taps: [Array], interceptors: [Array], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, thisCompilation: SyncHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, compilation: SyncHook { _args: [Array], taps: [Array], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, normalModuleFactory: SyncHook { _args: [Array], taps: [Array], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, contextModuleFactory: SyncHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, beforeCompile: AsyncSeriesHook { _args: [Array], taps: [], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, compile: SyncHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, make: AsyncParallelHook { _args: [Array], taps: [Array], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, afterCompile: AsyncSeriesHook { _args: [Array], taps: [], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, watchRun: AsyncSeriesHook { _args: [Array], taps: [], interceptors: [], call: undefined, promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, failed: SyncHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, invalid: SyncHook { _args: [Array], taps: [Array], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, watchClose: SyncHook { _args: [], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, infrastructureLog: SyncBailHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, environment: SyncHook { _args: [], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, afterEnvironment: SyncHook { _args: [], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, afterPlugins: SyncHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, afterResolvers: SyncHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, entryOption: SyncBailHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, infrastructurelog: SyncBailHook { _args: [Array], taps: [], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined } }, name: undefined, parentCompilation: undefined, outputPath: '', outputFileSystem: NodeOutputFileSystem { mkdirp: [Function: mkdirP] { mkdirP: [Circular], mkdirp: [Circular], sync: [Function: sync] }, mkdir: [Function: bound mkdir], rmdir: [Function: bound rmdir], unlink: [Function: bound unlink], writeFile: [Function: bound writeFile], join: [Function: bound join] }, inputFileSystem: CachedInputFileSystem { fileSystem: NodeJsInputFileSystem {}, _statStorage: Storage { duration: 60000, running: Map {}, data: Map {}, levels: [Array], count: 0, interval: null, needTickCheck: false, nextTick: null, passive: true, tick: [Function: bound tick] }, _readdirStorage: Storage { duration: 60000, running: Map {}, data: Map {}, levels: [Array], count: 0, interval: null, needTickCheck: false, nextTick: null, passive: true, tick: [Function: bound tick] }, _readFileStorage: Storage { duration: 60000, running: Map {}, data: Map {}, levels: [Array], count: 0, interval: null, needTickCheck: false, nextTick: null, passive: true, tick: [Function: bound tick] }, _readJsonStorage: Storage { duration: 60000, running: Map {}, data: Map {}, levels: [Array], count: 0, interval: null, needTickCheck: false, nextTick: null, passive: true, tick: [Function: bound tick] }, _readlinkStorage: Storage { duration: 60000, running: Map {}, data: Map {}, levels: [Array], count: 0, interval: null, needTickCheck: false, nextTick: null, passive: true, tick: [Function: bound tick] }, _stat: [Function: bound bound ], _statSync: [Function: bound bound ], _readdir: [Function: bound readdir], _readdirSync: [Function: bound readdirSync], _readFile: [Function: bound bound readFile], _readFileSync: [Function: bound bound readFileSync], _readJson: [Function], _readJsonSync: [Function], _readlink: [Function: bound bound readlink], _readlinkSync: [Function: bound bound readlinkSync] }, recordsInputPath: null, recordsOutputPath: null, records: {}, removedFiles: Set {}, fileTimestamps: Map {}, contextTimestamps: Map {}, resolverFactory: ResolverFactory { _pluginCompat: SyncBailHook { _args: [Array], taps: [Array], interceptors: [], call: [Function: lazyCompileHook], promise: [Function: lazyCompileHook], callAsync: [Function: lazyCompileHook], _x: undefined }, hooks: { resolveOptions: [HookMap], resolver: [HookMap] }, cache2: Map {} }, infrastructureLogger: [Function: logger], resolvers: { normal: { plugins: [Function: deprecated], apply: [Function: deprecated] }, loader: { plugins: [Function: deprecated], apply: [Function: deprecated] }, context: { plugins: [Function: deprecated], apply: [Function: deprecated] } }, options: { mode: 'development', context: 'E:\\echarts\\code', devtool: 'eval-cheap-module-source-map', node: { setImmediate: false, process: 'mock', dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', child_process: 'empty', console: false, global: true, Buffer: true, __filename: 'mock', __dirname: 'mock' }, output: { path: 'E:\\echarts\\code\\dist', filename: 'js/[name].js', publicPath: '/', chunkFilename: 'js/[name].js', globalObject: "(typeof self !== 'undefined' ? self : this)", webassemblyModuleFilename: '[modulehash].module.wasm', library: '', hotUpdateFunction: 'webpackHotUpdate', jsonpFunction: 'webpackJsonp', chunkCallbackName: 'webpackChunk', devtoolNamespace: '', libraryTarget: 'var', pathinfo: true, sourceMapFilename: '[file].map[query]', hotUpdateChunkFilename: '[id].[hash].hot-update.js', hotUpdateMainFilename: '[hash].hot-update.json', crossOriginLoading: false, jsonpScriptType: false, chunkLoadTimeout: 120000, hashFunction: 'md4', hashDigest: 'hex', hashDigestLength: 20, devtoolLineToLine: false, strictModuleExceptionHandling: false }, resolve: { alias: [Object], extensions: [Array], modules: [Array], plugins: [Array], unsafeCache: true, mainFiles: [Array], aliasFields: [Array], mainFields: [Array], cacheWithContext: true, preferAbsolute: true, ignoreRootsErrors: true, roots: [Array] }, resolveLoader: { modules: [Array], plugins: [Array], unsafeCache: true, mainFields: [Array], extensions: [Array], mainFiles: [Array], cacheWithContext: true }, module: { noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/, rules: [Array], unknownContextRequest: '.', unknownContextRegExp: false, unknownContextRecursive: true, unknownContextCritical: true, exprContextRequest: '.', exprContextRegExp: false, exprContextRecursive: true, exprContextCritical: true, wrappedContextRegExp: /.*/, wrappedContextRecursive: true, wrappedContextCritical: false, strictExportPresence: false, strictThisContextOnImports: false, unsafeCache: true, defaultRules: [Array] }, optimization: { splitChunks: [Object], minimizer: [Array], removeAvailableModules: false, removeEmptyChunks: true, mergeDuplicateChunks: true, flagIncludedChunks: false, occurrenceOrder: false, sideEffects: false, providedExports: true, usedExports: false, concatenateModules: false, runtimeChunk: undefined, noEmitOnErrors: false, checkWasmTypes: false, mangleWasmImports: false, namedModules: true, hashedModuleIds: false, namedChunks: true, portableRecords: false, minimize: false, nodeEnv: 'development' }, plugins: [ VueLoaderPlugin {}, [DefinePlugin], [CaseSensitivePathsPlugin], [FriendlyErrorsWebpackPlugin], [HtmlWebpackPlugin], [PreloadPlugin], [PreloadPlugin], [CopyPlugin], [HotModuleReplacementPlugin], [ProgressPlugin], HelloPlugin {} ], entry: { app: [Array] }, cache: true, target: 'web', performance: false, infrastructureLogging: { level: 'info', debug: false } }, context: 'E:\\echarts\\code', requestShortener: RequestShortener { currentDirectoryRegExp: /(^|!)E:\/echarts\/code/g, parentDirectoryRegExp: /(^|!)E:\/echarts\//g, buildinsAsModule: true, buildinsRegExp: /(^|!)E:\/echarts\/code\/node_modules\/_webpack@4\.46\.0@webpack/g, cache: Map {} }, running: false, watchMode: false, _assetEmittingSourceCache: WeakMap { <items unknown> }, _assetEmittingWrittenFiles: Map {}, watchFileSystem: NodeWatchFileSystem { inputFileSystem: CachedInputFileSystem { fileSystem: NodeJsInputFileSystem {}, _statStorage: [Storage], _readdirStorage: [Storage], _readFileStorage: [Storage], _readJsonStorage: [Storage], _readlinkStorage: [Storage], _stat: [Function: bound bound ], _statSync: [Function: bound bound ], _readdir: [Function: bound readdir], _readdirSync: [Function: bound readdirSync], _readFile: [Function: bound bound readFile], _readFileSync: [Function: bound bound readFileSync], _readJson: [Function], _readJsonSync: [Function], _readlink: [Function: bound bound readlink], _readlinkSync: [Function: bound bound readlinkSync] }, watcherOptions: { aggregateTimeout: 200 }, watcher: EventEmitter { _events: [Object: null prototype] {}, _eventsCount: 0, _maxListeners: undefined, options: [Object], watcherOptions: [Object], fileWatchers: [], dirWatchers: [], mtimes: [Object: null prototype] {}, paused: false, aggregatedChanges: [], aggregatedRemovals: [], aggregateTimeout: 0, _onTimeout: [Function: bound _onTimeout] } }}
简略的plugin写一个生成的动态资源文件
插件代码
class myPlugin { constructor(options) { // constructor构造函数接管new myPlugin(params)时传递的参数params console.log("我是new这个类时所传递的参数-->", options); } apply(compiler) { // console.log('^_^', compiler) // 下面有十分多api,可供使用(参见compiler打印后果) compiler.hooks.emit.tapAsync('lss',(compliation,cb)=>{ console.log('compliation',compliation.assets); const content=` - 生存不易 - 打工仔加油致力 - 奥利给 - ` compliation.assets['FBI-WARNING.md'] = { size() { return content.length }, source: function () { return content } } cb() }) }}module.exports = myPlugin
vue.config.js文件中应用插件
// 引入这个插件const myPlugin = require('./src/plugin/myPlugin') configureWebpack: { // 在plugins数组中实例化对象,若须要传参,变传递参数 plugins: [ new myPlugin('我是参数') ] },
未完待续。明天就先写(水)到这里吧