这是一篇关于webpack 4手工搭建重点问题的分析,webpack 3相关能够戳这里: https://github.com/Eleven90/webpack-pages-V3,这里并不试图从零手把手去堆代码,而是对其中的重点问题作稍微深刻一点的解读。某些细节这里若是没有说起,项目代码里多半已解决。
这是最新的 babel 配置,和网上的诸多教程可能有不一样,能够自行测试验证有效性。
基础依赖包css
npm i babel-loader@8 @babel/core -D
从 babel7 开始,全部的官方插件和主要模块,都放在了 @babel 的命名空间下。从而能够避免在 npm 仓库中 babel 相关名称被抢注的问题。
在 package.json 同级添加.babelrc 配置文件,先空着。html
{ "presets": [], // 预设 "plugins": [] // 插件 }
package.json 文件能够声明须要支持到的浏览器版本前端
package.json 中定义(推荐)node
"browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ],
更多定义格式请查看: browserslist
.babelrc 中定义(不推荐)jquery
{ "presets": [ [ "@babel/preset-env", { "targets": { "chrome": "58", "ie": "11" } } ] ] }
@babel/preset-env
或@babel/plugin-transform-runtime
,二选一便可。使用@babel/preset-envwebpack
安装依赖包:git
npm i @babel/preset-env @babel/polyfill -D
.babelrc 文件写上配置,@babel/polyfill 不用写入配置,会自动被调用。es6
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "entry", "modules": false, } ] ] }
配置参数github
modules
参数,"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false
,默认值是 auto。 useBuiltIns
参数,"usage" | "entry" | false
,默认值是 false。web
false
:须要在 js 代码第一行主动 import '@babel/polyfill',会将@babel/polyfill 整个包所有导入。 entry
:须要在 js 代码第一行主动 import '@babel/polyfill',会将 browserslist 环境不支持的全部垫片都导入。 usage
:项目里不用主动 import,会自动将代码里已使用到的、且 browserslist 环境不支持的垫片导入。 targets
参数,用来配置须要支持的的环境,不只支持浏览器,还支持 node。若是没有配置 targets 选项,就会读取项目中的 browserslist 配置项。loose
参数,默认值是 false,若是 preset-env 中包含的 plugin 支持 loose 的设置,那么能够经过这个字段来作统一的设置。使用@babel/plugin-transform-runtime
安装依赖包
npm i @babel/plugin-transform-runtime -D
若是配置参数 corejs 未设置或为 false,需安装依赖@babel/runtime
(这部分代码会被抽离并打包到应用 js 里,因此能够安装在 dependencies 里),仅对 es6 语法转译,而不对新 API 转译。
npm i @babel/runtime
若是配置参数 corejs 设置为 2,需安装依赖@babel/runtime-corejs2
(同上,推荐安装在 dependencies 里。),对语法、新 API 都转译。
npm i @babel/runtime-corejs2
corejs:2
,可是,检测不到‘hello‘.includes(‘h‘)
这种句法,因此存在必定隐患,书写代码时需注意。.babelrc 文件写上配置
{ "presets": [], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 2 // 推荐 } ] ] }
corejs
,默认值是 false,只对语法进行转换,不对新 API 进行处理;当设置为 2 的时候,须要安装@babel/runtime-corejs2
,这时会对 api 进行处理。helpers
,默认值是 true,用来开启是否使用 helper 函数来重写语法转换的函数。useESModules
,默认值是 false,是否对文件使用 ES 的模块语法,使用 ES 的模块语法能够减小文件的大小。@babel/preset-env
仍是@babel/plugin-transform-runtime
? (传送门:babel polyfill 和 runtime 浅析)
@babel/preset-env + @babel/polyfill
能够转译语法、新 API,但存在污染全局问题;@babel/plugin-transform-runtime + @babel/runtime-corejs2
,可按需导入,转译语法、新 API,且避免全局污染(babel7 中@babel/polyfill 是@babel/runtime-corejs2 的别名),可是检测不到‘hello‘.includes(‘h‘)这种句法;@babel/polyfill 和@babel/runtime-corejs2 都使用了 core-js(v2)这个库来进行 api 的处理。
core-js(v2)这个库有两个核心的文件夹,分别是 library 和 modules。@babel/runtime-corejs2 使用 library 这个文件夹,@babel/polyfill 使用 modules 这个文件夹。
library 和 modules 包含的文件基本相同,最大的不一样是_export.js 这个文件:
// core-js/modules/_exports.js var global = require('./_global'); var core = require('./_core'); var hide = require('./_hide'); var redefine = require('./_redefine'); var ctx = require('./_ctx'); var PROTOTYPE = 'prototype'; var $export = function (type, name, source) { var IS_FORCED = type & $export.F; var IS_GLOBAL = type & $export.G; var IS_STATIC = type & $export.S; var IS_PROTO = type & $export.P; var IS_BIND = type & $export.B; var target = IS_GLOBAL ? global : IS_STATIC ? global[name] || (global[name] = {}) : (global[name] || {})[PROTOTYPE]; var exports = IS_GLOBAL ? core : core[name] || (core[name] = {}); var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {}); var key, own, out, exp; if (IS_GLOBAL) source = name; for (key in source) { // contains in native own = !IS_FORCED && target && target[key] !== undefined; // export native or passed out = (own ? target : source)[key]; // bind timers to global for call from export context exp = IS_BIND && own ? ctx(out, global) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; // extend global if (target) redefine(target, key, out, type & $export.U); // export if (exports[key] != out) hide(exports, key, exp); if (IS_PROTO && expProto[key] != out) expProto[key] = out; } }; global.core = core; // type bitmap $export.F = 1; // forced $export.G = 2; // global $export.S = 4; // static $export.P = 8; // proto $export.B = 16; // bind $export.W = 32; // wrap $export.U = 64; // safe $export.R = 128; // real proto method for `library` module.exports = $export;
// core-js/library/_exports.js var global = require('./_global'); var core = require('./_core'); var ctx = require('./_ctx'); var hide = require('./_hide'); var has = require('./_has'); var PROTOTYPE = 'prototype'; var $export = function (type, name, source) { var IS_FORCED = type & $export.F; var IS_GLOBAL = type & $export.G; var IS_STATIC = type & $export.S; var IS_PROTO = type & $export.P; var IS_BIND = type & $export.B; var IS_WRAP = type & $export.W; var exports = IS_GLOBAL ? core : core[name] || (core[name] = {}); var expProto = exports[PROTOTYPE]; var target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE]; var key, own, out; if (IS_GLOBAL) source = name; for (key in source) { // contains in native own = !IS_FORCED && target && target[key] !== undefined; if (own && has(exports, key)) continue; // export native or passed out = own ? target[key] : source[key]; // prevent global pollution for namespaces exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] // bind timers to global for call from export context : IS_BIND && own ? ctx(out, global) // wrap global constructors for prevent change them in library : IS_WRAP && target[key] == out ? (function (C) { var F = function (a, b, c) { if (this instanceof C) { switch (arguments.length) { case 0: return new C(); case 1: return new C(a); case 2: return new C(a, b); } return new C(a, b, c); } return C.apply(this, arguments); }; F[PROTOTYPE] = C[PROTOTYPE]; return F; // make static versions for prototype methods })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; // export proto methods to core.%CONSTRUCTOR%.methods.%NAME% if (IS_PROTO) { (exports.virtual || (exports.virtual = {}))[key] = out; // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME% if (type & $export.R && expProto && !expProto[key]) hide(expProto, key, out); } } }; // type bitmap $export.F = 1; // forced $export.G = 2; // global $export.S = 4; // static $export.P = 8; // proto $export.B = 16; // bind $export.W = 32; // wrap $export.U = 64; // safe $export.R = 128; // real proto method for `library` module.exports = $export;
例如对Promise的转译,@babel/polyfill和@babel/runtime-corejs2的转译方式差别以下:
var p = new Promise(); // @babel/polyfill require("core-js/modules/es6.promise"); var p = new Promise(); // @babel/runtime-corejs2 var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault"); var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise")); var a = new _promise.default();
综合上面的分析,得出结论:
若是是本身的应用: @babel/preset-env + @babel/polyfill
useBuiltIns
设置为entry
比较不错。import '@babel/polyfill'
,或在webpack的入口entry中写入模块@babel/polyfill
,会将browserslist环境不支持的全部垫片都导入;‘hello‘.includes(‘h‘)
这种句法,足够安全且代码体积不是特别大,推荐使用!useBuiltIns
设置为usage
。node_modules/
目录,若是使用到的第三方包有个别未作好ES6转译,有遇到bug的可能性,而且检测不到‘hello‘.includes(‘h‘)
这种句法。useBuiltIns
设置为false
比较不错。
在js代码第一行import '@babel/polyfill'
,或在webpack的入口entry中写入模块@babel/polyfill
,会将@babel/polyfill整个包所有导入;
最安全,但打包体积会大一些,通常不选用。
须要安装的所有依赖:
npm i babel-loader@8 @babel/core @babel/preset-env -D npm i @babel/polyfill
.babelrc配置文件
{ "presets": [ [ "@babel/preset-env", { "modules": false, // 推荐 "useBuiltIns": "entry", // 推荐 } ] ], "plugins": [] }
若是是开发第三方类库: @babel/plugin-transform-runtime + @babel/runtime-corejs2
;
(或者,不作转码处理,提醒使用者本身作好兼容处理也能够。)
须要安装的所有依赖:
npm i babel-loader@8 @babel/core @babel/plugin-transform-runtime -D npm i @babel/runtime-corejs2
.babelrc配置文件
{ "presets": [], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 2 // 推荐 } ] ] }
Babel 官方认为,把不稳定的 stage0-3 做为一种预设是不太合理的,@babel/preset-env
、@babel/polyfill
等只支持到stage-4
级别,所以 babel 新版本废弃了 stage 预设,转而让用户本身选择使用哪一个 proposal 特性的插件,这将带来更多的明确性(用户无须理解 stage,本身选的插件,本身便能明确的知道代码中可使用哪一个特性)。
全部建议特性的插件,都改变了命名规范,即相似 @babel/plugin-proposal-function-bind
这样的命名方式来代表这是个 proposal 阶段特性。
因此,处于建议阶段的特性,基本都已从@babel/preset-env
、@babel/polyfill
等包中被移除,须要本身去另外安装对应的 preset、plugin,(通常你能找到的名称里有 proposal 字样的包,须要本身在@babel/preset-env
、@babel/plugin-transform-runtime
之外作配置)。
各个级别当前能够选用的 proposal 插件大概以下(传送门):
{ "plugins": [ // Stage 0 "@babel/plugin-proposal-function-bind", // Stage 1 "@babel/plugin-proposal-export-default-from", "@babel/plugin-proposal-logical-assignment-operators", ["@babel/plugin-proposal-optional-chaining", { "loose": false }], ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }], ["@babel/plugin-proposal-nullish-coalescing-operator", { "loose": false }], "@babel/plugin-proposal-do-expressions", // Stage 2 ["@babel/plugin-proposal-decorators", { "legacy": true }], "@babel/plugin-proposal-function-sent", "@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-numeric-separator", "@babel/plugin-proposal-throw-expressions", // Stage 3 "@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-import-meta", ["@babel/plugin-proposal-class-properties", { "loose": false }], "@babel/plugin-proposal-json-strings" ] }
配置装饰器语法支持
安装依赖
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
.babelrc 增长配置
{ "presets": [], "plugins": [ [ "@babel/plugin-proposal-decorators", // @babel/plugin-proposal-decorators须要在@babel/plugin-proposal-class-properties以前 { "legacy": true // 推荐 } ], [ "@babel/plugin-proposal-class-properties", { "loose": true // babel编译时,对class的属性采用赋值表达式,而不是Object.defineProperty(更简洁) } ] ] }
配置 import 动态导入支持
安装依赖
npm i @babel/plugin-syntax-dynamic-import -D
.babelrc 文件增长配置
{ "presets": [], "plugins": [ "@babel/plugin-syntax-dynamic-import", ] }
正常若是有多个入口,须要在 entry 中,以对象形式将全部入口都配置一遍,html 模版目录也须要 new 不少个 HtmlWebpackPlugin 来配置对应的页面模版,是否能够自动扫描? 不管多少个入口,只管新建,而不用管理入口配置?能够的!
安装 node 模块 glob ( 扫描文件就靠它了 ).
npm i glob -D
const glob = require('glob')
自动扫描获取入口文件、html 模版(统一放在 utils.js 文件里)
/** * 获取文件 * @param {String} filesPath 文件目录 * @returns {Object} 文件集合(文件名: 文件路径) */ const getFiles = filesPath => { let files = glob.sync(filesPath) let obj = {} let filePath, basename, extname for (let i = 0; i < files.length; i++) { filePath = files[i] extname = path.extname(filePath) // 扩展名 eg: .html basename = path.basename(filePath, extname) // 文件名 eg: index // eg: { index: '/src/views/index/index.js' } obj[basename] = path.resolve(appDirectory, filePath) } return obj } /** * 打包入口 * 1.容许文件夹层级嵌套; * 2.入口js的名称不容许重名; */ const entries = getFiles('src/views/**/*.js') /** * 页面的模版 * 1.容许文件夹层级嵌套; * 2.html的名称不容许重名; */ const templates = getFiles('src/views/**/*.html') /** * 获取entry入口,为了处理在某些时候,entry入口会加 polyfill等: * 1.容许文件夹层级嵌套; * 2.入口的名称不容许重名; * * @returns {Object} entry 入口列表(对象形式) */ const getEntries = () => { let entry = {} for (let name in entries) { entry[name] = entries[name] } return entry }
webpack 打包入口
module.exports = { entry: utils.getEntries(), }
html 模版自动引入打包资源(区分 dev 和 prod 环境,配置不一样,一样抽离到 utils.js 文件更好一些)
/** * 生成webpack.config.dev.js的plugins下new HtmlWebpackPlugin()配置 * @returns {Array} new HtmlWebpackPlugin()列表 */ const getHtmlWebpackPluginsDev = () => { let htmlWebpackPlugins = [] let setting = null for (let name in templates) { setting = { filename: `${name}.html`, template: templates[name], inject: false, // js插入的位置,true/'head'/'body'/false } // (仅)有入口的模版自动引入资源 if (name in getEntries()) { setting.chunks = [name] setting.inject = true } htmlWebpackPlugins.push(new HtmlWebpackPlugin(setting)) setting = null } return htmlWebpackPlugins } /** * 生成webpack.config.prod.js的plugins下new HtmlWebpackPlugin()配置 * @returns {Array} new HtmlWebpackPlugin()列表 */ const getHtmlWebpackPluginsProd = () => { let htmlWebpackPlugins = [] let setting = null for (let name in templates) { setting = { filename: `${name}.html`, template: templates[name], minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, inject: false, // js插入的位置,true/'head'/'body'/false } // (仅)有入口的模版自动引入资源 if (name in getEntries()) { setting.chunks = ['manifest', 'vendor', 'common', name] setting.inject = true } htmlWebpackPlugins.push(new HtmlWebpackPlugin(setting)) setting = null } return htmlWebpackPlugins }
须要安装的依赖包
npm i less less-loader css-loader style-loader postcss-loader postcss-preset-env postcss-import cssnano postcss-safe-parser mini-css-extract-plugin -D
过去版本的autoprefixer、postcss-cssnext已内置在postcss-preset-env内。
配置
默认会将 css 一块儿打包到 js 里,借助 mini-css-extract-plugin 将 css 分离出来并自动在生成的 html 中 link 引入(过去版本中的 extract-text-webpack-plugin 已不推荐使用)。
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
{ test: /\.(less|css)$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'], } // 在启用dev-server时,mini-css-extract-plugin插件不能使用contenthash给文件命名 => 因此本地起dev-server服务调试时,使用style-loader // USE_HMR是自定义的环境变量,意思是是否使用了热替换中间件 const styleLoader = process.env.USE_HMR ? 'style-loader' : MiniCssExtractPlugin.loader // 经过其余合适的方式判断是否为本地调试环境也同样,自由选择。 const styleLoader = process.env.BUILD_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader { test: /\.(less|css)$/, use: [styleLoader, 'css-loader', 'postcss-loader', 'less-loader'], },
// 单独使用link标签加载css并设置路径,相对于output配置中的publickPath new MiniCssExtractPlugin({ filename: 'static/css/[name].[contenthash:7].css', // 注意这里使用的是contenthash,不然任意的js改动,打包时都会致使css的文件名也跟着变更。 chunkFilename: 'static/css/[name].[contenthash:7].css', })
PostCSS 自己不会对你的 CSS 作任何事情, 你须要安装一些 plugins 才能开始工做.
参考文档:postcss GitHub 文档
在 package.json 同级目录新建 postcss.config.js 文件:
module.exports = { // parser: 'sugarss', // 是一个以缩进为基础的语法,相似于 Sass 和 Stylus,https://github.com/postcss/sugarss plugins: { 'postcss-import': {}, 'postcss-preset-env': {}, 'cssnano': {}, 'postcss-flexbugs-fixes': {}, } }
经常使用的插件:
less 是预处理,而 PostCSS 是后处理,基本支持 less 等预处理器的功能,自动添加浏览器厂商前缀向前兼容,容许书写下一代 css 语法 ,能够在编译时去除冗余的 css 代码,PostCSS 声称比预处理器快 3-30 倍. 由于 PostCSS,可能咱们要放弃 less/sass/stylus 了。
css 中引入的图片( 或其它资源 ) ==> url-loader
配置了 url-loader 之后,webpack 编译时能够自动将小图转成 base64 编码,将大图改写 url 并将文件生成到指定目录下 ( file-loader 能够完成文件生成,可是不能小图转 base64,因此统一用 url-loader,但 url-loader 在处理大图的时候是自动去调用 file-loader,因此你仍然须要 install file-loader )。
// 处理图片(file-loader来处理也能够,url-loader更适合图片) { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'static/assets/images/[name].[hash:7].[ext]', }, }, // 处理多媒体文件 { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'static/assets/media/[name].[hash:7].[ext]', }, }, // 处理字体文件 { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'static/assets/fonts/[name].[hash:7].[ext]' } },
html 页面中引入的图片( 或其它资源 ) ==> html-loader
css 中的图片 url-loader 处理便可,而 html 中 img 标签引入的图片,不作工做的状况下: 图片将不会被处理,路径也不会被改写,即最终编译完成后这部分图片是找不到的,怎么办? html-loader !( 这个时候你应该是 url-loader 和 html-loader 都配置了,因此 css 中图片、页面引入的图片、css 中的字体文件、页面引入的多媒体文件等, 通通都会在编译时被处理 )。
// html中引用的静态资源在这里处理,默认配置参数attrs=img:src,处理图片的src引用的资源. { test: /\.html$/, loader: 'html-loader', options: { // 除了img的src,还能够继续配置处理更多html引入的资源(不能在页面直接写路径,又须要webpack处理怎么办?先require再js写入). attrs: ['img:src', 'img:data-src', 'audio:src'], minimize: false, removeComments: true, collapseWhitespace: false } }
有的时候, 图片可能既不在 css 中, 也不在 html 中引入, 怎么办?
import img from 'xxx/xxx/123.jpg' 或 let img = require('xxx/xxx/123.jpg')
js 中引用 img,webpack 将会自动搞定它。
图片等资源的访问路径问题:
通过上面的处理,静态资源处理基本没有问题了,webpack 编译时将会将文件打包到你指定的生成目录,可是不一样位置的图片路径改写会是一个问题.
所有经过绝对路径访问便可,在 output 下的 publicPath 填上适当的 server 端头,来保证全部静态资源文件路径能被访问到,具体要根据服务器部署的目录结构来作修改。
output: { path: path.resolve(__dirname, 'dist'), // 输出目录的配置,模板、样式、脚本、图片等资源的路径配置都相对于它 publicPath: '/', // 模板、样式、脚本、图片等资源对应的server上的路径 }
html-webpack-plugin插件,配置:
const HtmlWebpackPlugin = require('html-webpack-plugin')
new HtmlWebpackPlugin({ favicon: './src/assets/img/favicon.ico', // favicon路径,经过webpack引入同时能够生成hash值 filename: './views/index.html', // 生成的html存放路径,相对于path template: './src/views/index.html', // html模板路径 title: '首页', // 页面title meta: '', // 容许插入meta标签,如=>meta: {viewport: 'width=device-width,initial-scale=1, shrink-to-fit=no'} inject: 'body', // js插入的位置,true/'head'/'body'/false hash: true, // 为静态资源生成hash值(js和css) chunks: ['vendors', 'index'], // 须要在此页面引入的chunk,不配置就会引入全部页面的资源 minify: { // 压缩html文件 removeComments: true, // 移除html中的注释 collapseWhitespace: true, // 删除空白符与换行符 }, })
script-loader 把咱们指定的模块 JS 文件转成纯字符串, exports-loader 将须要的 js 对象 module.exports 导出, 以支持 import 或 require 导入.
安装依赖包
npm i script-loader exports-loader -D
配置
{ test: require.resolve('zepto'), loader: 'exports-loader?window.Zepto!script-loader' }
以上是正常处理一个 _"能够 NPM 安装但又不符合 webpack 模块化规范" 的库, 例如其它库 XX, 处理后能够直接 import xx from XX 后使用; 可是, zepto 有点特殊, 默认 npm 安装的包或者从 github clone 的包, 都是仅包含 5 个模块, 其它如经常使用的 touch 模块是未包含的, 想要正常使用还需作得更多._
a) 打包出一个包含更多须要模块的 zepto 包
从 github clone 官方的包下来, 找到名为 make 的文件 ( 在 package.json 同级目录 ), 用记事本打开, 找到这一行 modules = (env['MODULES'] || 'zepto event ajax form ie').split(' ')
, 应该是在第 41 行, 手动修改加入你想要引入的模块, 而后保存;
b) 在 make 文件同级目录 => 右键打开终端或 git bash => 敲 npm i 安装 zepto 源码须要的 node 包 ( 这里你应当是已经已安装过 nodejs 了, 若是没有, 安装好后再作这一步 ), 等待安装结束.
c) 在刚才打开的 终端/git bash 敲命令 npm run-script dist, 若是没有报错, 你应该在这个打开的文件夹里能够看到生成了一个文件夹 dist, 打开它, 包含新模块的 zepto 包就在这了, Over !
拿到新的 zepto 包后, 建议放到本身的 src 下 lib 目录( 第三方工具包目录 ), 再也不经过 npm 的方式去安装和更新 zepto 了 ( 由于未来 npm update 后的 zepto 又将缺乏模块,未来别人也会出现误操做 ); 如今开始对这个放在 lib 目录下的 zepto.min.js 进行处理:
a) 经过 script-loader、exports-loader 转成符合 webpack 模块化规范的包
{ // # require.resolve()是nodejs用来查找模块位置的方法,返回模块的入口文件 test: require.resolve('./src/js/lib/zepto.min.js'), loader: 'exports-loader?window.Zepto!script-loader' }
b) 给模块配置别名
resolve: { alias: { 'zepto': path.resolve(__dirname, './src/js/lib/zepto.min.js') } }
c) 自动加载模块, 再也不处处 import 或 require
new webpack.ProvidePlugin({ $: 'zepto', Zepto: 'zepto', })
大功告成, 如今使用 zepto 跟你使用 jquery 或其它 node 包是同样的开发体验了 !
以上, 演示的是对于一个第三方库( 不能 npm 安装,也不符合 webpack 规范 ), 如何去处理, 达到和正常 npm 安装同样的开发体验, 仅就 zepto 来讲, npm 库有符合 webpack 规范的不一样版本 ( zepto-webpack, 或 zepto-modules), 有须要能够试试.
平时意图使用某个包, 先去 NPM 官网搜一搜比较好.
某些时候,应用中依赖了某些模块,但但愿将这些模块独立经过CDN引入,以减少包的体积,因此没必要将这些模块打包,例如:jQuery。特定场景下,这个功能会有用武之地!
module.exports = { ... output: { ... }, externals: { jquery: "jQuery" }, ... }
一般打包js库会选择rollup,可是webpack一样能够作到,若是是须要对css、图片等有较多应用的js库,webpack会有更多优点,因rollup对样式、图片的处理能力是比较弱的。
配置
打包出全部环境均可以使用的包—— umd
module.exports = { ... entry: { sdk: 'xxxxxxx.js', }, output: { ... library: '[name]', libraryTarget: 'umd', libraryExport: 'default', umdNamedDefine: true, // 会对 UMD 的构建过程当中的 AMD 模块进行命名,不然就使用匿名的 define }, ... }
应用导出
export default { a: xxxx, b: xxxx, c: xxxx, }
打包出的js,将支持import、requrie导入,script标签导入,能够经过window.sdk使用等:
// import import { a, b, c } from '........js' // require const anything = require('........js') // window window.sdk window.sdk.a // node global.sdk global.sdk.a
知识扩展:
安装依赖包
npm i webpack-dev-server -D
经常使用配置
devServer: { contentBase: path.join(__dirname, 'static'), // # 告诉服务器从哪里提供内容(默认当前工做目录) host: 'localhost', // # 默认localhost,想外部可访问用'0.0.0.0' openPage: 'views/index.html', // # 指定默认启动浏览器时打开的页面 index: 'views/index.html', // # 指定首页位置 port: 9090, // # 默认8080 inline: true, // # 能够监控js变化 hot: true, // # 热启动 open: true, // # 自动打开浏览器 compress: true, // # 一切服务都启用gzip 压缩 watchContentBase: true // # contentBase下文件变更将reload页面(默认false) }
运行命令 ( package.json 配置命令 => npm run dev )
"dev": "cross-env BUILD_ENV=development webpack-dev-server --mode development --colors --profile"
根据目录结构的不一样, contentBase、openPage 参数要配置合适的值, 不然运行时应该不会马上访问到你的首页; 同时要注意你的 publicPath, 静态资源打包后生成的路径是一个须要思考的点, 这与你的目录结构有关。
某些时候,你可能想要build出前端代码后,直接在本地访问看看结果。能够经过修改publicPath来变动静态资源引用路径,或者起一个本地服务来访问。
新建 prod.server.js 文件
let express = require('express') let compression = require('compression') let app = express() let port = 9898 app.use(compression()) app.use(express.static('./static/')) module.exports = app.listen(port, function(err) { if (err) { console.log(err) return } console.log('Listening at http://localhost:' + port + '\n') })
运行命令
node prod.server.js
访问路径
localhost:9898/views/
比本身配置一个 express 服务更简洁的方式,去访问打包后的资源。
安装依赖
npm i http-server -D
package.json 配置命令
"scripts": { "http-server": "http-server dist" }
运行命令
npm run http-server
访问路径
localhost:8080 或 http://127.0.0.1:8080
安装依赖
npm i eslint eslint-loader eslint-friendly-formatter babel-eslint -D
eslint-friendly-formatter,指定终端中输出eslint提示信息的格式。
增长配置
{ test: /\.js$/, enforce: 'pre', loader: 'eslint-loader', include: [paths.appSrc], exclude: [ /node_modules/, ], options: { formatter: require('eslint-friendly-formatter'), }, },
package.json
文件同级增长文件.eslintrc.js
module.exports = { "root": true, "parserOptions": { "sourceType": "module", }, "parser": "babel-eslint", // eslint未支持的js新特性先进行转换 "env": { "browser": true, "es6": true, "node": true, "shared-node-browser": true, "commonjs": true, }, "globals": { // 设置全局变量(false:不容许重写;) "BUILD_ENV": false, }, "extends": "eslint:recommended", // 使用官方推荐规则,使用其余规则,须要先install,再指定。 "rules": { } }
配置项含义:
若是有须要跳过检查的文件/文件夹,新建.eslintignore
文件
/node_modules
参考文档
使用happypack来优化,多进程运行编译,参考文档: