【前端工程化】

前端工程户核心技术之模块化

前端模块化是一种标准,不是实现。commonjs是前端模块化的标准,而nodejs实现了一套commonjs的规范。
什么是前端模块化:将复杂程序根据规范拆分成若干个模块,每个模块间组织是有逻辑的,一个模块包括输入和输出,每个模块内部实现时私有的,对外暴露接口与其他模块通信,而不是直接调用的关系。这其实就是一种典型的面向对象编程思想。一个html页面可以引用的script标签包括:脚本和模块
脚本和模块的区别:请添加图片描述
原来我们所有的业务逻辑代码只能写在index.js里,但用了模块化标准以后,就可以对他进行拆分了,可以把他拆分为一个入口文件entry.js,这个入口文件去引用若干个模块,然后把他们组织起来进行调用,浏览器去调用时,主要访问入口文件,再由入口文件调用其他模块化文件,组装成一个业务逻辑。脚本和模块化最典型的区别就是,通过模块化拆分后,我们可以更清楚的看到整个业务的逻辑,如果只有一个index.js你并不知道源码里面在干什么。

前端模块化的进化过程

请添加图片描述
定义的函数是挂载在window上的,如果有一个文件夹里也定义api函数,会引发全局命名空间冲突
请添加图片描述
请添加图片描述
请添加图片描述

commonjs规范介绍

commonjs是nodejs默认模块化规范,每个文件就是一个模块,有自己的作用域,可以维护自己的私有变量,node中cjs模块加载采用同步加载方式,必须等这个模块加载完了再去执行后面的代码,通过require加载模块,通过exports或者module.exports输出模块。
commonjs规范特点:

  1. 所有代码都运行在模块作用域,没有js脚本概念,写一个js文件就是一个模块,所以不会污染全局作用域。
  2. 模块可以多次加载,第一次加载时会运行模块,模块输出结果会被缓存,再次加载时,会从缓存结果中直接读取模块输出结果。
  3. 模块加载的顺序,按照其在代码中出现的顺序
  4. 模块输出的值是值的拷贝,类似iife方案中的内部变量

commonjs规范示例

每个功能创建一个文件,如api文件:

const handle = require('./handle')
function api(){
	return {
		code: 0,
		data:{
			a: 1,
			b: 2,
		}
	}
}
module.exports = {
	api,handle
}

handle.js文件:

function handle(data, key){
	return data.data[key]
}

module.exports = handle;

sum.js文件:

function sum(a, b){
	return a+b;
}

module.exports = sum;

然后在entry.js中引入各文件,执行相关业务逻辑:
请添加图片描述
请添加图片描述
首先,我们需要有一个主模块(即入口文件entry.js),在主模块中,我们会使用require加载模块,require加载模块时会把模块变成module对象,module对象中有一个load方法,通过load方法进行模块加载,在加载过程中,他会在模块的外层包一层,把原来的模块变成自适应函数,我们写的代码(比如写在api.js中的代码)变成函数里面的内容了,他会向自适应函数传入一些变量(require、module、exports、_filename、_dirname),这就是为什么在node里可以直接使用require的原因。使用module.exports输出模块时,最终输出结果会被缓存到module cache map中,这是一个键值对,键是module path+module name,值是module.exports。commonjs输出模块时,module.exports只能输出一个结果

commonjs模块打包

安装browserify:npm install browserify -g
打包命令:browserify module_test/cjs/entry.js -o dist/bundle.js
⚠️当存在多个entry.js模块时,每个entry.js模块都需要单独打包
browserify打包原理:

  1. 本质还是通过自执行函数实现模块化
  2. 将每个模块编号,存入一个对象,每个模块标记依赖模块
  3. 实现了require方法,核心是通过call方法调用模块,并传入require、module、exports方法,通过module存储模块信息,通过exports存储模块输出信息

amd规范、cmd规范

amd规范采用非同步(异步)加载模块,允许指定回调函数。node模块通常位于本地,加载速度快,所以适用于同步加载,但是在浏览器中,如果用同步加载会阻塞模块渲染(整个页面的渲染),所以在浏览器环境下,模块需要请求获取,适用于异步加载,所以诞生了amd规范,用来做异步加载,require.js是amd的一个具体实现库,amd、cmd目前很少使用,因为目前我们主要使用node和浏览器开发,分别使用commonjs和esmoudle。
cmd规范整合了commonjs和amd的优点,模块加载是异步的,cmd专门用于浏览器端,sea.js是cmd规范的一个实现,amd和cmd最大的问题是没有通过语法升级解决模块化,他们定义模块还是通过调用js方法来生成模块,这种方式利于我们快速应用,但是他没有办法对模块化进行规模化的应用,因为他们没有实现标准的语法规范
ESMoudle规范设计理念是希望在编译时就确定模块以来关系及输入输出,commonjs和amd必须在运行时才能确定依赖和输入、输出,ESMoudle通过import加载模块,通过exports输出模块
commonjs和ESMoudle规范的对比:

  1. commonjs模块输出的是值的拷贝,也就是说commonjs模块内部的值你想改是改不了的,但是es6模块输出的是值的引用
    使用es6定义test.js文件:
export let a = 1;
export function plus() {
	a++;
}

定义entry.js文件:

import {a, plus} from './test.js'
console.log(a);//1
plus();
console.log(a);//2

使用commonjs定义test.js:

let a = 1;
exports.a = a;
exports.plus = function(){
	a++;
}
// 上述代码等同于:
// module.exports={
//     a:1,// 这个a的值是直接拷贝过来的
//     plus// plus里加的是模块里的a
// }

定义entry.js文件:

const { a, plus} = require(./test.js);
console.log(a);// 1
plus();
console.log(a);// 1

如果想要获取模块里的a,需要使用get方法,在commonjs定义的test.js中定义get方法:

let a = 1;
exports.a = a;
exports.plus = function(){
	a++;
}
exports.get = function(){
	return a;
}

在entry.js文件中调用get:

const { a, plus, get} = require(./test.js);
console.log(a);// 1
plus();
console.log(a);// 1
console.log(get());// 2
  1. commonjs模块是运行时加载,es6模块是编译时输出接口,在编译时就能确定导入哪些模块,输出哪些模块
  2. commonjs是单个值导出,es6可以导出多个
  3. commonjs模块为同步加载,es6支持异步加载,导出的是一个promise
    请添加图片描述
  4. commonjs的this是当前模块的输出值,es6的this是undefined
  5. commonjs和es6的语法不同
    请添加图片描述
    script脚本和模块对比:模块具备更高的开发效率,可以将复杂的代码拆分成若干简单的代码,代码可读性强,复用高效,但是模块在加载过程中会损耗性能,因为模块文件很多,会使得加载速度变得更慢,而脚本具有更高的页面性能。模块在浏览器中运行会存在兼容性问题,要特别注意。所以在浏览器中运用模块化存在一些局限性,比如浏览器缺乏模块管理能力,模块分散在各个项目中,模块性能加载慢,无法在大型项目中直接使用,这两个问题是npm(解决模块管理能力问题)和webpack(解决模块加载慢问题)核心解决的问题

前端工程化关键技术之npm+webpack原理

npm实现的初步思路:

  1. 集中管理所有模块,所有模块都上传到仓库(registry)
  2. 模块内创建package.json标注模块的基本信息
  3. 通过nom publish发布模块,上传到仓库(registry)
  4. 通过nom install安装模块,模块安装到node_modules目录

npm解决的核心问题是模块管理问题,比如模块开发好了之后上传到哪里?如何进行快速复用?npm包含cli、模块仓库、官网三大部分。
请添加图片描述
首先,你产生了一个模块module1,需要通过npm init初始化或者说创建一个模块,常见的模块他只会包含package.json,在package.json里面我们通过修改name、version、dependencies确定模块的基本信息,如果这个模块想加载另外一个模块,那你需要通过npm install加载,加载完成后,另外一个模块会被下载到node_modules目录下,另外一个模块也是包含package.json和node_modules目录,这就是npm的规范。当module1开发完了之后,执行npm publish将其上传到npm仓库,仓库包含两部分,一个是公开部分,可以任意使用,但其中又分为普通仓库和组织的仓库;一个是private部分,也就是私有仓库。
npm原理总结:npm init创建模块,npm install安装模块,npm publish发布模块,npm link模块进行本地开发,npm config查看/调整本地配置,npm run调用package.json中的scripts。
npm规范:package.json管理模块信息,node_modules保存依赖
npm的局限:npm只能解决模块的高效管理和获取问题,无法解决页面性能加载问题,模块化发明后,制约其广泛应哟哦那个的因素就是性能问题
webpack实现原理
请添加图片描述
webpack主要是将所有的资源进行打包,打包到一个文件中。在原来的html模式中,如果你要去加载一些js,你需要通过script标签,如果你要去加载css,你需要编写css并做一个资源加载,这样在html中,就需要加载很多资源,如果通过webpack构建,首先会把js分为entry.js,然后在其中调用不同模块文件,这么多的资源如果按照原来的模式我们需要加载很多资源,但是webpack从entry.js开始,分析所有依赖并进行打包,然后将他们合成到一个bundle.js文件中,这样html在加载时就只用加载一个资源了,提高了页面性能
webpack的原理:最初的webpack核心解决的问题就是代码合并和拆分,webpack的核心里面是将资源都视为模块,统一进行打包和处理。webpack提供了loader和plugins完成功能扩展。

webpack

核心概念

entry:入口模块文件路径
output:输出bundle文件路径
module:模块,webpack构建对象
bundle:输出文件,webpack构建产物
chunk:中间文件,webpack构建的中间产物
loader:文件转换器,使用vue-loader工具让浏览器能运行vue文件,将vue文件输出为js文件
plugin:插件,执行特定任务,比如说将js文件依次分割成不同的文件,如png、css、js等

有一个js文件作为入口文件,这个js文件就是entry,他会引用不同的js模块、css模块,所有的这些都是从entry开始打包,然后依次去查找他们的依赖路径,将这个entry打包成bundle文件,在打包过程中会生成一些中间文件,这些中间文件就叫chunk

快速入门

终端执行:npm init初始化项目
创建src/index.js文件
创建public/index.html
创建webpack.config.js并填入配置

const path = require('path')

module.exports = {
	mode:'development',
	entry:'./src/index.js',
	output:{
		path:path.resolve(__dirname,'.dist'),// 这里必须使用path.resolve生成绝对路径,否则会报错。__dirname指向webpack.config.js所在完整路径,然后这个完整路径会拼上./dist
		filename:'bundle.js'
	}
}

执行npm install -D webpack -cli
配置build命令为webpack
请添加图片描述
执行npm run build完成打包构建
通过查看打包后的bundle文件发现,bundle本质上是通过自适应函数实现模块化里面的私有化作用域
请添加图片描述

source-map原理讲解

在webpack.config.js中加入devtool选项,devtool的配置项可以从官网获得:

const path = require('path')

module.exports = {
	mode:'development',
	devtool:'source-map',
	entry:'./src/index.js',
	output:{
		path:path.resolve(__dirname,'.dist'),// 这里必须使用path.resolve生成绝对路径,否则会报错。__dirname指向webpack.config.js所在完整路径,然后这个完整路径会拼上./dist
		filename:'bundle.js'
	}
}

执行npm run build打包后的文件跟不加devtool的有很大区别
请添加图片描述
sourceMappingURL=bundle.js.map这句话其实就是指定bundle.js对应的source-map文件的路径,即bundle.js.map。source-map维护了一个json文件,
请添加图片描述
file表示bundle.js.map对应的源码文件bundle.js;mapping表示我对bundle.js的哪一行代码进行映射,;表示换行,映射的内容为sourceContent的内容,AAAA是vlq码,可以看一下阮一峰老师的文章;source表示我们在浏览器中会生成一个新的资源文件资源文件内容就是sourceContent

通过webpack loader打包css文件

在index.js中引入css可以把css文件也打包到js中:import './index.css';,这样在webpack打包时,可以对css进行模块化。安装css-loader:cnpm i -D css-loader,还需要安装style-loader:cnpm i -D style-loader,在webpack.config.js中添加配置:

const path = require('path')

module.exports = {
	mode:'development',
	devtool:'source-map',
	entry:'./src/index.js',
	output:{
		path:path.resolve(__dirname,'.dist'),// 这里必须使用path.resolve生成绝对路径,否则会报错。__dirname指向webpack.config.js所在完整路径,然后这个完整路径会拼上./dist
		filename:'bundle.js'
	},
	module:{
		// module主要是针对模块化的一些内容
		rules:[
			// rules是个数组,配置的所有loader都在这里面
		{
			test:/.css$/,// 匹配所有以css结尾的文件
			use:['style-loader','css-loader']// loader的执行顺序是从上到下,从右到左,所以一定是先执行css-loader,再执行style-loader
		}]
	}
}

执行npm run build打包后,css被打包进了js中。css-loader会将css文件模块化,style-loader再将这个模块转化为dom,所以二者的执行顺序不能变。style-loader会在index.html中添加style标签,然后将css代码写入style标签中,通过这种方式将css和js结合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值