npm link
说明
npm link: 本地调试 npm 模块
npm-sum (npm包)
项目结构
mkdir npm-sum
npm init -y
touch index.js
//index.js
const sum = (...args) => {
return args.reduce((prev, next) => prev + next, 0)
}
module.exports = sum
npm link
方式将本地开发包链接到全局 node_modules/下
报错
权限问题
解决方法,用管理员权限打开power shell
example (测试项目)
项目结构
mkdir example
npm init -y
touch index.js
//index.js
const sum = require('npm-sum')
console.log(sum(1, 2, 3, 4, 5))
npm link npm-sum
安装本地npm包(npm-sum),到自己的项目下,会生成在node_modules下
运行项目
node index.js
与npm-sum解除关联
npm unlink npm-sum
参考
原生webpack
最简单的webpack怎么运行的
目录结构
初始化
npm init -y
cnpm install webpack webpack-cli -D
src
// src/base/b.js
module.exports = 'b'
// src/a.js
let b = require('./base/b.js')
module.exports = 'a' + b
// src/index.js
let str = require('./a.js')
console.log(str)
webpack.config.js
// webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {},
plugins: []
}
运行
根目录下运行命令
webpack
浏览器中查看
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
实现自己的webpack
初始化
webpack-dev(项目)
mkdir webpack-dev
npm init -y
zf-pack(类似webpack的npm包)
mkdir zf-pack
npm init -y
修改package.json
//package.json
{
"name": "zf-pack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
//新增
"bin": {
"zf-pack": "./bin/zf-pack.js"
}
}
新增bin
bin/zf-pack.js
#! /usr/bin/env node
console.log('start')
关联运行
zf-pack
cd zf-pack //管理员权限
npm link
webpack-dev
npm link zf-pack
npx zf-pack
zf-pack拿到webpack-dev中的代码
webpack-dev
webpack.config.js
// webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {},
plugins: []
}
src目录
// src/base/b.js
module.exports = 'b'
// src/a.js
let b = require('./base/b.js')
module.exports = 'a' + b
// src/index.js
let str = require('./a.js')
console.log(str)
zf-pack
bin
// /bin/zf-pack.js
#! /usr/bin/env node
// console.log('start')
let path = require('path')
// // config配置文件
let config = require(path.resolve('webpack.config.js'))
let Compiler = require('../lib/Compiler.js')
let compiler = new Compiler(config)
// 标识运行编译
compiler.run()
lib
// lib/Compiler.js
let fs = require('fs')
let path = require('path')
class Compiler {
constructor (config) {
// entry output
this.config = config
// 需要保存入口文件的路径
this.entryId // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {}
this.entry = config.entry // 入口路径
// 工作路径
this.root = process.cwd()
}
getSource (modulePath) {
let content = fs.readFileSync(modulePath, 'utf8')
return content
}
buildModule (modulePath, isEntry) {
let source = this.getSource(modulePath)
// 模块id modulePath = modulePath- this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath)
console.log('source', source)
console.log('moduleName', moduleName)
}
run () {
this.buildModule(path.resolve(this.root, this.entry), true)
}
}
module.exports = Compiler
重新发布运行
zf-pack
npm link
webpack-dev
npm link zf-pack
npx zf-pack
AST递归解析
安装依赖
cnpm install babylon @babel/types @babel/traverse @babel/generator -S
增加parse方法
let fs = require('fs')
let path = require('path')
let babylon = require('babylon')
let t = require('@babel/types')
let traverse = require('@babel/traverse').default
let generator = require('@babel/generator').default
class Compiler {
constructor (config) {
// entry output
this.config = config
// 需要保存入口文件的路径
this.entryId // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {}
this.entry = config.entry // 入口路径
// 工作路径
this.root = process.cwd()
}
getSource (modulePath) {
let content = fs.readFileSync(modulePath, 'utf8')
return content
}
parse (source, parentPath) {
let ast = babylon.parse(source)
let dependencies = [] // 依赖的数组
traverse(ast, {
CallExpression (p) {
// a() require()
let node = p.node // 对应的节点
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__'
let moduleName = node.arguments[0].value // 取到的就是模块的引用名字
moduleName = moduleName + (path.extname(moduleName) ? '' : '.js')
moduleName = './' + path.join(parentPath, moduleName)
;('src/a.js')
dependencies.push(moduleName)
node.arguments = [t.stringLiteral(moduleName)]
}
}
})
let sourceCode = generator(ast).code
return { sourceCode, dependencies }
}
buildModule (modulePath, isEntry) {
let source = this.getSource(modulePath)
// 模块id modulePath = modulePath- this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath)
if (isEntry) {
this.entryId = moduleName // 保存入口的名字
}
// 解析需要把source源码进行改造 返回一个依赖列表
let { sourceCode, dependencies } = this.parse(
source,
path.dirname(moduleName)
) // ./src
console.log(sourceCode, dependencies)
// 把相对路径和模块中的内容 对应起来
this.modules[moduleName] = sourceCode
dependencies.forEach(dep => {
// 附模块的加载 递归加载
this.buildModule(path.join(this.root, dep), false)
})
}
run () {
this.buildModule(path.resolve(this.root, this.entry), true)
}
}
module.exports = Compiler
运行
生成打包结果
ejs
注意不要格式化ejs
// main.ejs
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
return __webpack_require__(__webpack_require__.s = "<%-entryId%>");
})
({
<%for(let key in modules){%>
"<%-key%>":
(function (module, exports, __webpack_require__) {
eval(`<%-modules[key]%>`);
}),
<%}%>
});
增加 emitFile
let fs = require('fs')
let path = require('path')
let babylon = require('babylon')
let t = require('@babel/types')
let traverse = require('@babel/traverse').default
let generator = require('@babel/generator').default
let ejs = require('ejs')
class Compiler {
constructor (config) {
// entry output
this.config = config
// 需要保存入口文件的路径
this.entryId // './src/index.js'
// 需要保存所有的模块依赖
this.modules = {}
this.entry = config.entry // 入口路径
// 工作路径
this.root = process.cwd()
}
getSource (modulePath) {
// ./index.less
let content = fs.readFileSync(modulePath, 'utf8')
return content
}
parse (source, parentPath) {
let ast = babylon.parse(source)
let dependencies = [] // 依赖的数组
traverse(ast, {
CallExpression (p) {
// a() require()
let node = p.node // 对应的节点
if (node.callee.name === 'require') {
node.callee.name = '__webpack_require__'
let moduleName = node.arguments[0].value // 取到的就是模块的引用名字
moduleName = moduleName + (path.extname(moduleName) ? '' : '.js')
moduleName = './' + path.join(parentPath, moduleName)
;('src/a.js')
dependencies.push(moduleName)
node.arguments = [t.stringLiteral(moduleName)]
}
}
})
let sourceCode = generator(ast).code
return { sourceCode, dependencies }
}
buildModule (modulePath, isEntry) {
let source = this.getSource(modulePath)
// 模块id modulePath = modulePath- this.root src/index.js
let moduleName = './' + path.relative(this.root, modulePath)
if (isEntry) {
this.entryId = moduleName // 保存入口的名字
}
// 解析需要把source源码进行改造 返回一个依赖列表
let { sourceCode, dependencies } = this.parse(
source,
path.dirname(moduleName)
) // ./src
console.log(sourceCode, dependencies)
// 把相对路径和模块中的内容 对应起来
this.modules[moduleName] = sourceCode
dependencies.forEach(dep => {
// 附模块的加载 递归加载
this.buildModule(path.join(this.root, dep), false)
})
}
emitFile () {
// 发射文件
// 用数据 渲染我们的
// 拿到输出到哪个目录下 输出路径
let main = path.join(this.config.output.path, this.config.output.filename)
// 模板的理解
let templateStr = this.getSource(path.join(__dirname, 'main.ejs'))
let code = ejs.render(templateStr, {
entryId: this.entryId,
modules: this.modules
})
this.assets = {}
// 资源中 路径对应的代码
this.assets[main] = code
fs.writeFileSync(main, this.assets[main])
}
run () {
this.buildModule(path.resolve(this.root, this.entry), true)
this.emitFile()
}
}
module.exports = Compiler
打包后的js放会浏览器中运行
loader
less-loader
zf-pack
修改getSource方法
getSource (modulePath) {
// ./index.less
let rules = this.config.module.rules
let content = fs.readFileSync(modulePath, 'utf8')
// 拿到每个规则来处理
for (let i = 0; i < rules.length; i++) {
let rule = rules[i]
let { test, use } = rule
let len = use.length - 1
if (test.test(modulePath)) {
// 这个模块需要通过loader来转化
// loader获取对应的loader函数
function normalLoader () {
let loader = require(use[len--])
// 递归调用loader 实现转化功能
content = loader(content)
if (len >= 0) {
normalLoader()
}
}
normalLoader()
}
}
return content
}
webpack-dev
// loader/less-loader.js
let less = require('less');
function loader(source){
let css = '';
less.render(source,function (err,c) {
css = c.css;
});
css = css.replace(/\n/g,'\\n');
return css;
}
module.exports = loader
// loader/style-loader.js
function loader(source) {
let style = `
let style = document.createElement('style');
style.innerHTML = ${JSON.stringify(source)}
document.head.appendChild(style);
`
return style;
}
module.exports = loader;
// webpack.config.js
let path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.less$/,
use: [
path.resolve(__dirname, 'loader', 'style-loader'),
path.resolve(__dirname, 'loader', 'less-loader')
]
}
]
},
plugins: []
}