阅读 127

npm link

npm link

说明

npm link: 本地调试 npm 模块

image.png

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/下

image.png

报错

权限问题

1629165545(1).png

解决方法,用管理员权限打开power shell

image.png

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
复制代码

image.png

与npm-sum解除关联

npm unlink npm-sum
复制代码

image.png

参考

如何使用 npm-link

原生webpack

最简单的webpack怎么运行的

目录结构

1629167336(1).png

初始化

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
复制代码

image.png

浏览器中查看

// 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

1629172500(1).jpg

初始化

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
复制代码

image.png

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
复制代码

image.png

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

复制代码

运行

1629181689(1).jpg

生成打包结果

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放会浏览器中运行

1629187770(1).png

image.png

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: []
}

复制代码

运行

image.png

文章分类
前端
文章标签