历史:WebPage ==> WebApp \
前端工程化
技术选型 --> vue / react / element / ant (手写 --> 浏览器识别 : 工程化转译)
统一规范 --> eslint / husky (代码规范)
测试、部署、监控 --> ut、e2e、mock
性能优化 --> 懒加载、module 拆分 模块化重构 --> SPA、多页、多chunk
WebPack 满足工程化的工具 类似的gulp、jade、pug、grunt
WebPack 面试: 基本配置 -- 场景应用 -- 扩展开发
WebPack 基础使用
入口:run dev > 查看 package.json 找到 dev build/build.js
- 检查版本 build/build.js
require('./check-versions')() // 检查node和npm版本
./check-versions
// 开辟子进程执行cmd指令,并且返回
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
// node和npm版本需求
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
// 依次判断版本是否符合要求
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
// 警告输出
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
- 指定环境变量 build/build.js
process.env.NODE_ENV = 'production' // 指定环境变量
- loading 插件及其他配置
const ora = require('ora') // 一个美美的loading插件
const rm = require('rimraf') // rm -rf Node版本的unix命令
const path = require('path') // node自带的文件路径插件
const chalk = require('chalk') // 控制台高亮
const webpack = require('webpack')
const config = require('../config') // 配置入口
const webpackConfig = require('./webpack.prod.conf') // webpack配置
- config 三套环境 并行并且相互隔离的入口
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
// webpack编译的开始
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
-
开发环境
5.1 dev 和 base 的配置合并webpack.dev.conf.js
// dev.conf 的配置 和 base.conf的配置合并 const devWebpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) },5.2 本地 server 的配置
devServer: { clientLogLevel: 'warning', historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, hot: true, contentBase: false, // since we use CopyWebpackPlugin. compress: true, host: HOST || config.dev.host, port: PORT || config.dev.port, open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, publicPath: config.dev.assetsPublicPath, proxy: config.dev.proxyTable, // 面试题:如何本地项目去做一些多端口服务的代理转发?? quiet: true, // necessary for FriendlyErrorsPlugin watchOptions: { poll: config.dev.poll, }面试题:如何本地项目去做一些多端口服务的代理转发??
通过配置proxyTable (../config/index.js)
proxyTable: { '/api': { target: 'http://localhost:8080/', pathRewrite: { '^/api': '/static/mock' } },包括改变端口号
// Various Dev Server settings host: 'localhost', // can be overwritten by process.env.HOST port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined autoOpenBrowser: false, errorOverlay: true, notifyOnErrors: true, poll: false,5.3 plugins 微内核:像一个底座,插入各种插件
plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/dev.env') }), new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. new webpack.NoEmitOnErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin ...面试常考的插件:
- HtmlWebpackPlugin 生产 html 模版文件:
// 生成html模板文件 new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }),面试题: 如何利用webpack去做依赖锁定?不想写在npm 中,外部外链一个依赖
锁依赖固定板本 script
- CopyWebpackPlugin 面试题:如何静态文件的移动&赋值
new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), // 打包完成后,这里patch 会配置成 dist 文件夹,打包完成后,会对static下的文件移动到目标文件夹 to: config.dev.assetsSubDirectory, ignore: ['.*'] } ]) -
生产环境
6.1 代码压缩// 代码压缩 new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false } }, sourceMap: config.build.productionSourceMap, parallel: true }),6.2 分trunk app与manifest 分开
如何通过webpack进行性能优化?
分trunk 结合路由 进行懒加载,一个路由指定到一个trunk 上,其他的部分不加载,优化了加载速度。
loader 与 plugin
loader: 编译过程中的通道,对编译过程中拿到内容,转换再输出 module - solve
plugin: 编译的生命周期时,有额外功能的拓展
性能优化如何去做?
2.1 tree shaking -->
// elementUI
import { select, button, tip } from elementUI
Vue.use(select)
Vue.use(button)
Vue.use(tip)
// lodash
import { deepEqual, equal } from lodash
equal(a, b)
通过以上的方式,webpack在打包的时候,不会将整个依赖打包进去,而只会通过tree shaking 将使用的函数打包
2.2 模块懒加载 --> vue-router + trunk
不同的router 加载 不同的 trunk,页面加载某个url的时候就不会加载整个页面的代码
2.3 noParse
2.4 webpack 升级
2.5 文件指纹:chunkhash
JS函数式编程
特点
- Vue 3 React 16.8 全面化函数式的推动
- 函数式编程可以使得代码但愿相对更佳独立 - tree shaking 过程更加顺畅,更方便做UT
- 减少了对this的依赖,减轻了开发人员对于指向问题的困惑
- js天生友好函数式:ramda、lodash
概念
- 一种抽象运算过程
- 函数式的函数并非对于过程运算,是函数的映射,提供一个函数的功能
- 幂等 - 相同的输入始终得到相同的输出
纯函数 - 幂等
// 取数组的前三项
let arr = [1,2,3,4,5];
arr.slice(0, 3) // [1,2,3]
arr.slice(0, 3) // [1,2,3]
arr.splice(0, 3) // [1,2,3]
arr.splice(0, 3) // [4,5]
// 这里splice就不再是幂等了,因为结果和执行次数有关,相同输出不会得到相同结果,非纯函数
对于系统的改造
// 不纯的函数
let min = 18;
let limit = age => age > min;
// limit 依赖外部,依赖环境
// 纯的函数
let limit = age => age > 18;
对于大型系统来说,对于外部状态的依赖,会大大的提高系统的复杂性;
- 问题: 18被硬编码到了函数内部的,造成了功能拓展的局限
高阶函数HOC
定义:
- 函数作为参数被传递到另一个函数中
- 函数作为返回值被另一个函数返回
let fn = arg => {
let outer = 'outer';
let innerFn = () => {
console.log(outer);
console.log(arg);
}
return innerFn;
}
let closure = fn(18);
// 闭包
面试问题:内存泄露?
尽量不写闭包 如果无法避免闭包,代码层面可以结合生命周期,销毁;或者让函数 = null; 工程化方面,可以放在chunk里,自动垃圾回收
函数柯里化
传递给函数一部分参数用于功能调用,让他返回一个函数去处理剩下的参数
let add = (x, y) => x + y;
// 柯里化后
let add = x => (y => x + y);
let add2 = add(2);
let add200 = add(200);
add2(2); // 2 + 2 add(2)(2)
add200(50); // 200 + 50
// 回到上面的limit,纯函数化
let limit = min => (age => age > min);
let limit18 = limit(18);
limit18(20) //true;
柯里化是一种预加载方式,通过这个方式一方面生成函数,一方面执行函数
- 问题 包心菜代码的产生h(g(f(x)));
组合
通过更优雅的方式实现纯函数的解耦
let compose = (f, g) => (x => f(g(x)));
let add1 = x => x + 1;
let mul5 = x => x * 5;
compose(mul5, add1)(2); //15
面试题: 数组长度位置的情况下,拿到最后一项
可以用reverse,但是改变了arr,不纯 利用解耦的思想
let compose = (f, g) => (x => f(g(x)));
let first = arr => arr[0];
let reverse = arr => arr.reverse();
let last = compose(first, reverse);
last([1,2,3,4,5]); //5
基于webpack进行开发
-
loader
1.1 通用写法 webpack.base.config.js loader
{ test: /\.js$/, loader: path.resolve(__dirname, 'loaders', 'loaderA') } // loaders/loaderA.js module.exports = function(content, map, meta) { console.log('I\`m loaderA'); return content; }柯里化先内层函数,后外层,因此在打包的时候先显示最外层的。 => 可以通过 module.exports.pitch 方法改过来
module.exports.pitch = function() { console.log('pitch A'); }1.2 同步方式
module.exports = function(content, map, meta) { console.log('I\`m loaderA'); this.callback(null, content, map, meta); }1.3 异步方式
module.exports = function(content, map, meta) { console.log('I\`m loaderA'); const callback = this.async(); setTimeout(() => { callback(null, content); }, 1000) }1.4 loader 路径统一管理
resolveLoader: { modules: [ 'node_modules', path.resolve(__dirname, 'loaders') ] } -
plugins 都是类,使用的时候都会new 一个实例
console.log(compiler.hooks);
// 不同的声明周期
compiler.hooks.emit.tap('Plugin1', Compilation => {
console.log('hooks.emit.tap');
})
compiler.hooks.afterEmit.tap('Plugin1', Compilation => {
console.log('hooks.afterEmit.tap');
})
// 不同的函数
// 处理异步函数
compiler.hooks.emit.tap('Plugin1', (Compilation, cb) => {
setTimeout(() => {
console.log('hooks.emit ASYNC');
cb();
}, 1000)
})
// 处理promise
compiler.hooks.emit.tap('Plugin1', Compilation => {
return new Promise(resolve => {
setTimeout(() => {
console.log('hooks.emit ASYNC');
resolve();
}, 1000)
})
})
module.exports = PluginA;