在开发npm包时遇到的一些问题以及解决方法(zzy-javascript-devtools)

当前文章停止更新! 最新地址

目录文件

## command 命令文件
## lib -- 转义生成的代码
## node_modules
## src -- 源代码
## .gitignore
## index.js -- 入口文件
## package.json
## readme.md

命令步骤

分解(parsing) -> 转译(babel) -> 压缩(minfiles)

React转化为ES5代码(包含所有ES6转化为ES5代码)

核心是利用babel的一组预设 @babel/preset-react 来对react进行转化,随后再使用 @babel/env 来进行ES6 - ES5的转化

1.npm包内安装babel相关依赖

{
  "devDependencies": {
    "@babel/preset-react": "^7.13.13",
    "@babel/cli": "^7.13.14",
    "@babel/core": "^7.13.15",
    "@babel/polyfill": "^7.12.1",
    "@babel/preset-env": "^7.13.15",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
  },
}

2. 配置bebel文件(babel7 -> babel.config.json)

babel文件的读取顺序是由下往上的

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "edge": "17",
          "firefox": "60",
          "chrome": "67",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": "3.6.5"
      }
    ],
    [ "@babel/preset-react" ]
  ]
}

3. 使用babel命令进行转译

 ./node_modules/.bin/babel src --copy-files --out-dir lib --presets=@babel/env,@babel/preset-react 

src - 转译src目录内的文件
–out-dir - 表示编译的类型是文件
–copy-files - 拷贝一份不进行编译的文件到目标文件夹
–presets=@babel/env - 添加预设(@babel/env,@babel/preset-react)

注意项

暂无。

这样就完成了对React文件和所有ES6的转化!

参考链接:
babel官网
如何使用ES6編写一个 React模块,并且编译后发布到NPM

利用fs + UglifyJS完成对 .js 文件的压缩与混淆

核心就是利用fs模块获取文件列表,递归获取文件信息并存储,然后在原来的架子上写入 .min.js 文件,最后再将 .js 文件删除

  • 原来的架子是指在进行babel转化的时候已经将目录搭好,我们只需要将新文件按原路径写入即可,省去了自己主动去创建文件夹的操作

废话不多说,直接上完整代码,搭配注释便能理解。

let fs = require('fs');
let UglifyJS = require('uglify-js');

// .css 文件手动压缩
function iGetInnerText(testStr) {
  var resultStr = testStr.replace(/\ +/g, ""); //去掉空格
  resultStr = testStr.replace(/[ ]/g, "");    //去掉空格
  resultStr = testStr.replace(/[\r\n]/g, ""); //去掉回车换行
  return resultStr;
}

// 递归对每个文件进行写入
function writefs(obj, toPath, pPath = toPath) {
  for (let i in obj) {
    if (!fs.lstatSync(`${pPath + '/'}${i}`).isDirectory()) {
      let tPath = pPath.replace(toPath, toPath)
      fs.writeFile(`${tPath}/${obj[i].toFileName}`, obj[i].code, 'utf-8', function (err) {
        if (err) throw err;
        console.log('success');
        if (i.indexOf('.js') > 0 || i.indexOf('.less') > 0) {
          // 写入完成删除源文件
          fs.unlinkSync(`${tPath}/${i}`)
        }
      })
    } else writefs(obj[i], toPath, `${pPath + '/'}${i}`)
  }
}

// 递归拿到所有文件,并重命名、获取文件信息
function getAllFiles(pathTo, obj = {}) {
  // 读取当前文件夹
  let nowLevelFiles = fs.readdirSync(pathTo)
  nowLevelFiles.forEach(i => {
    // 判断是否是文件夹
    if (!fs.lstatSync(`${pathTo}/${i}`).isDirectory()) {
      let newI = i.replace('.js', '.min.js')
      // 拿到文件内容
      let fileContent = fs.readFileSync(`${pathTo}/${i}`, 'utf-8'), fileType = i.split('.')[1]
      obj[i] = {
        form: `${pathTo}/${i}`,
        toFileName: newI,
        // 如果是 .js 文件,利用 UglifyJS 进行压缩,混淆,如果不是则利用正则消除空格
        code: fileType === 'js' ? UglifyJS.minify({ [i]: fileContent }).code : iGetInnerText(fileContent),
      }
    } else obj[i] = getAllFiles(`${pathTo}/${i}`, {})
  })
  return obj
}

let Uglify = function (toPath) {
  writefs(getAllFiles(toPath), toPath);
}

Uglify('./lib');
4-21

优化了一下 uglify.js ,将命令文件归拢一处,更加简洁

新增getFiles.js

let fs = require('fs');
module.exports = function getFiles(path) {
  let files = []
  getAllFiles(path)
  // 递归拿到所有文件,并重命名、获取文件信息
  function getAllFiles(path) {
    // 读取当前文件夹
    let nowLevelFiles = fs.readdirSync(path)
    nowLevelFiles.forEach(i => {
      // 判断是否是文件夹
      if (!fs.lstatSync(`${path}/${i}`).isDirectory()) {
        files.push({fileName: i, path: `${path}/${i}`, parentPath: path})
      } else getAllFiles(`${path}/${i}`, {})
    })
  }
  return files
}

优化 uglify.js
简单来说就是将递归获取所有文件的步骤摘了出去,作为一个功能函数来使用,更加符合 “美感”(?)。然后就是多了一个筛选,可以选择不进行压缩的文件。

let fs = require('fs');
let UglifyJS = require('uglify-js');
let getFiles = require('./getFiles')
const noCompress = ['JSBriged.js'] // 不需要压缩的文件名称

// .css 文件手动压缩
function iGetInnerText(testStr) {
  var resultStr = testStr.replace(/\ +/g, ""); //去掉空格
  resultStr = testStr.replace(/[ ]/g, "");    //去掉空格
  resultStr = testStr.replace(/[\r\n]/g, ""); //去掉回车换行
  return resultStr;
}

// 对每个文件进行写入
function writefs(obj, toPath, pPath = toPath) {

  let allFiles = getFiles(toPath)

  for (let i in obj) {
    fs.writeFile(obj[i].newPath, obj[i].code, 'utf-8', function (err) {
      if (err) throw err;
      console.log('success');
      if (i.indexOf('.js') > 0 || i.indexOf('.less') > 0) {
        // 写入完成删除源文件
        fs.unlinkSync(obj[i].form)
      }
    })
  }
}

// 拿到所有文件,并重命名、获取文件信息
function setFiles(pathTo) {
  let allFiles = getFiles(pathTo)
  let obj = {}
  allFiles.map((item, index) => {
    // 排除
    if (noCompress.indexOf(item.fileName) !== -1) return
    let newI = item.fileName.replace('.js', '.min.js')
    // 拿到文件内容
    let fileContent = fs.readFileSync(item.path, 'utf-8'), fileType = item.fileName.split('.')[1]
    obj[item.fileName] = {
      form: item.path,
      toFileName: newI,
      newPath: `${item.parentPath}/${newI}`,
      // 如果是 .js 文件,利用 UglifyJS 进行压缩,混淆,如果不是则利用正则消除空格
      code: fileType === 'js' ? UglifyJS.minify({ [item.fileName]: fileContent }).code : iGetInnerText(fileContent),
    }
  })
  return obj
}

let Uglify = function (toPath) {
  writefs(setFiles(toPath), toPath);
}

Uglify('./lib');

5-31 – 按需加载

前几天实现了按需加载,今天记录一下实现方式

step1: 更改所有文件内的导出方式

递归导入所有模块。由于第二步需要利用fs进行操作,所以导出需要node识别,也就是使用commonJS导入导出 (module.exports = {导出的模块})

step2: 分离(parsing)

所有模块引入之后,利用 递归 + fs模块 将每个方法(fn)循环生成到文件夹(原文件名)内部,每个方法额外拼接 export default,中途记录每个文件的名称及路径,在生成完毕之后动态生成 index.js ,利用模版字符串动态将每个文件以ES6形式导入导出

由于react组件的特殊性(.jsx 内部引入react(主要原因) ) 不参与分割,本身进行开发的时候就以模块形式开发,不聚一起。 所以在这里直接跳过分割,只进行文件拷贝(copyFiles)

// parsing.js
const fs = require('fs');
const path = require('path')
const getFiles = require('./getFiles')
const copyFiles = require('./copyFiles')

/*
  导入主文件
  获取所有导出值
  利用fs遍历生成新文件
  新建一个外部index,将所有遍历生成的文件引入其中 动态模版生成
*/

// index.js 到目标 文件的路径

class Parsing {
  // originPath - 源路径    targetPath - 目标路径   mainPath - 入口文件路径
  constructor(originPath, targetPath, mainPath) {
    this.path = originPath
    this.fileDetail = {} // 文件路径数据存储
    this.storePath = './lib/' // 最终文件存储路径
    this.resolvePath = targetPath
    this.indexPath = mainPath
  }
  init() {
    try {
      fs.mkdirSync(this.resolvePath)
    } catch { }
    // react component 拷贝一份到目标文件夹,不参与分割
    copyFiles(this.path, this.resolvePath, 'ReactComponents')

    // 拿到所有目标目录及自文件路径集合
    let files = getFiles(this.path)
    // 排除 不是 .js 后缀形式文件    排除 react component  另外处理
    files = files.filter(item => {
      let parentN = item.parentPath.split('/')
      return path.extname(item.fileName) === '.js' && parentN[parentN.length - 1] !== 'ReactComponents'
    })
    // 拿到当前文件夹下的所有文件  目前仅一层,若优化使用递归
    for (let file of files) {
      // 获取文件内的所有导出
      let fileExportFnList = require(file.path)
      let dirName = file.fileName.split('.')[0]
      // 写入文件夹
      try {
        fs.mkdirSync(`${this.resolvePath}/${dirName}`)
      } catch { }
      // 遍历生成新文件
      this.mkFile(fileExportFnList, dirName)
    }
    this.createIndex()
  }
  // 遍历生成新文件
  mkFile(fileExportFnList, dirName) {
    let dirDatas = []
    for (let i in fileExportFnList) {
      let exportFn = fileExportFnList[i]
      dirDatas.push({
        path: `${this.storePath}${dirName}/${i}.js`,
        name: i
      })
      let filePath = `${this.resolvePath}/${dirName}/${i}.js`
      fs.writeFileSync(filePath, 'export default ' + exportFn, 'utf-8')
    }
    // 记录每一个文件内所有导出模块(新文件)的路径及模块名称
    this.fileDetail[dirName] = dirDatas
  }
  //自动生成index.js
  createIndex() {
    let contentData = ''
    let exportCon = ''
    let fileD = this.fileDetail
    let RCFiles = getFiles(`${this.resolvePath}/ReactComponents`)
    // 筛选出后缀仅为 .jsx 的文件 且对后缀进行替换并将格式与 fileDetail 同步
    RCFiles = RCFiles.filter(i => path.extname(i.fileName) === '.jsx').map(j => {
      return {
        path: `${this.storePath}${j.path.split('separate')[1].replace('.jsx', '.js')}`,
        name: j.fileName.split('.jsx')[0]
      }
    })
    fileD = { ...fileD, 'ReactComponents': RCFiles }
    let dirNames = ''
    // 合并统一处理入口文件的导入
    for (let dir in fileD) {
      let names = ''
      dirNames += dir + ', '
      let files = fileD[dir]
      for (let i = 0; i < files.length; i++) {
        let { name, path } = files[i]
        names += name + ', '
        contentData +=
          `
import ${name} from '${path}'

`
      }
      // 版本向下兼容,将一个文件中的所有模块包裹一起,键名为文件名称
      contentData +=
        `
let ${dir} = { ${names.slice(0, names.length - 2)} }

`
      exportCon += names
    }
    // 删除额外 ,  号
    exportCon = exportCon.slice(0, exportCon.length - 2)
    dirNames = dirNames.slice(0, dirNames.length - 2)
    let finallyData =
      `
${contentData}

export { ${exportCon}, ${dirNames} }

`
    // 合并后写入
    fs.writeFileSync(this.indexPath, finallyData, 'utf-8')


  }
}

const parsing = new Parsing(
  path.resolve(__dirname, '../src'),
  path.resolve(__dirname, '../separate'),
  path.resolve(__dirname, '../index.js')
)

parsing.init()

step3 babel转化

这个点主要注意的是在babel转化时需要将转化模块更改为 ES6导入导出(默认commonJS)

babel.config.json

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false // 更改为 ES6导入导出
      }
    ],
    ["@babel/preset-react"]
  ]
}

step4 uglify压缩

和之前一样,不贴代码了

小结

之所以将第一、第三步分开,主要还是node不识别ES6导入导出,所以只能自己以cmooonJS形式去导出,然后在拆分时再前设 export default,这样便符合了 treeshaking 的前置要求

如果有问题还请评论区留言,或 Q: 884823636 联系本人。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值