nextapps-de / flexsearch

Next-Generation full text search library for Browser and Node.js
Apache License 2.0
12.53k stars 491 forks source link

Usage in ESM environment #341

Open Akryum opened 2 years ago

Akryum commented 2 years ago

I'm not able to use the latest version (0.7.31) in an ESM environment, such as a Vite app (browser-side) or a native-ESM node app (with TypeScript). The document doesn't also doesn't have anything about this.


The situation was a little bit better in version 0.7.21 with imports like this:


Vite:

import * as flexsearch from 'flexsearch'
import charset from 'flexsearch/dist/module/lang/latin/advanced.js'
import language from 'flexsearch/dist/module/lang/en.js'

Node ESM:

import flexsearch from 'flexsearch'

const flexsearchRoot = path.dirname(require.resolve('flexsearch/package.json'))
  return new flexsearch.Document({
    preset: 'match',
    document: {
      id: 'id',
      index: [
        'text',
      ],
    },
    charset: await loadModule(`${flexsearchRoot}/dist/module/lang/latin/advanced.js`),
    language: await loadModule(`${flexsearchRoot}/dist/module/lang/en.js`),
    tokenize: 'forward',
  })

(Notice we need a module loader such as jiti or vite-node to be able to load other files which are not marked as ESM).

rileyjshaw commented 2 years ago

I’m also having ESM issues in a Gatsby app:

// eslint error: Worker not found in 'flexsearch'
import { Worker } from "flexsearch";

// Build error: WebpackError: TypeError: flexsearch__WEBPACK_IMPORTED_MODULE_3__.Worker is not a constructor
const searchWorker = new Worker({ preset: "match" });

The weird thing is, Worker does exist in dev mode, and it’s a function. So there may be something going on during the build step with Webpack and this package’s module configuration.

I’ve tried pinning to 0.7.21 and 0.6, and that didn’t help. I also tried various other import types, including:

import FlexSearch from "flexsearch"; // or…
import * as FlexSearch from "flexsearch"; // or…
const FlexSearch = require("flexsearch");

const { Worker } = FlexSearch;

and they all have the same problem.


EDIT

I had some success by preventing new Worker from being called during SSR. Rather than creating searchWorker in the module body as shown above, I created it from within a componentDidMount-like hook as shown below:


import { Worker } from "flexsearch";

export default function ExampleComponent () {
  const searchWorker = useRef(null);
  useEffect(() => {
    searchWorker.current = new Worker({ preset: "match" });
  }, []);

  <div>
    <button onClick={() => searchWorker.current?.add("id", "value")}>Example</button>
  </div>
}
emersonbottero commented 2 years ago

I'm having the same issue.. how to import Index as an ESM module?

I'm using

import Index from "flexsearch/dist/module/index.js";

but on build I'm receiving using vite to create the bundle..

(node:10776) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. (Use node --trace-warnings ... to show where the warning was created) build error: C:\Users\emers\Source\Repos\mermaid-docs\node_modules.pnpm\flexsearch@0.7.31\node_modules\flexsearch\dist\module\index.js:1 import{SUPPORT_ENCODER,SUPPORT_CACHE,SUPPORT_ASYNC,SUPPORT_SUGGESTION,SUPPORT_SERIALIZE}from"./config.js";import{IndexInterface}from"./type.js";import{encode as default_encoder}from"./lang/latin/default.js";import{create_object,create_object_array,concat,sort_by_length_down,is_array,is_string,is_object,parse_option}from"./common.js";import{pipeline,init_stemmer_or_matcher,init_filter}from"./lang.js";import{global_lang,global_charset}from"./global.js";import apply_async from"./async.js";import{intersect}from"./intersect.js";import Cache,{searchCache}from"./cache.js";import apply_preset from"./preset.js";import{exportIndex,importIndex}from"./serialize.js";function Index(a,b){if(!(this instanceof Index))return new Index(a);let c,d,e;a?(SUPPORT_ENCODER&&(a=apply_preset(a)),c=a.charset,d=a.lang,is_string(c)&&(-1===c.indexOf(":")&&(c+=":default"),c=global_charset[c]),is_string(d)&&(d=global_lang[d])):a={};let f,g,h=a.context||{};this.encode=a.encode||c&&c.encode||default_encoder,this.register=b||create_object(),this.resolution=f=a.resolution||9,this.tokenize=e=c&&c.tokenize||a.tokenize||"strict",this.depth="strict"===e&&h.depth,this.bidirectional=parse_option(h.bidirectional,!0),this.optimize=g=parse_option(a.optimize,!0),this.fastupdate=parse_option(a.fastupdate,!0),this.minlength=a.minlength||1,this.boost=a.boost,this.map=g?create_object_array(f):create_object(),this.resolution_ctx=f=h.resolution||1,this.ctx=g?create_object_array(f):create_object(),this.rtl=c&&c.rtl||a.rtl,this.matcher=(e=a.matcher||d&&d.matcher)&&init_stemmer_or_matcher(e,!1),this.stemmer=(e=a.stemmer||d&&d.stemmer)&&init_stemmer_or_matcher(e,!0),this.filter=(e=a.filter||d&&d.filter)&&init_filter(e),SUPPORT_CACHE&&(this.cache=(e=a.cache)&&new Cache(e))}export default Index;Index.prototype.append=function(a,b){return this.add(a,b,!0)},Index.prototype.add=function(a,b,c,d){if(b&&(a||0===a)){if(!d&&!c&&this.register[a])return this.update(a,b);b=this.encode(""+b);const e=b.length;if(e){const d=create_object(),f=create_object(),g=this.depth,h=this.resolution;for(let j=0;j<e;j++){let i=b[this.rtl?e-1-j:j],k=i.length;if(i&&k>=this.minlength&&(g||!f[i])){let l=get_score(h,e,j),m="";switch(this.tokenize){case"full":if(2<k){for(let b=0;b<k;b++)for(let d=k;d>b;d--)if(d-b>=this.minlength){const g=get_score(h,e,j,k,b);m=i.substring(b,d),this.push_index(f,m,g,a,c)}break}case"reverse":if(1<k){for(let b=k-1;0<b;b--)if(m=i[b]+m,m.length>=this.minlength){const d=get_score(h,e,j,k,b);this.push_index(f,m,d,a,c)}m=""}case"forward":if(1<k){for(let b=0;b<k;b++)m+=i[b],m.length>=this.minlength&&this.push_index(f,m,l,a,c);break}default:if(this.boost&&(l=Math.min(0|l/this.boost(b,i,j),h-1)),this.push_index(f,i,l,a,c),g&&1<e&&j<e-1){const f=create_object(),h=this.resolution_ctx,k=i,l=Math.min(g+1,e-j);f[k]=1;for(let g=1;g<l;g++)if(i=b[this.rtl?e-1-j-g:j+g],i&&i.length>=this.minlength&&!f[i]){f[i]=1;const b=get_score(h+(e/2>h?0:1),e,j,l-1,g-1),m=this.bidirectional&&i>k;this.push_index(d,m?k:i,b,a,c,m?i:k)}}}}}this.fastupdate||(this.register[a]=1)}}return this};function get_score(a,b,c,d,e){return c&&1<a?b+(d||0)<=a?c+(e||0):0|(a-1)/(b+(d||0))*(c+(e||0))+1:0}Index.prototype.push_index=function(a,b,c,d,e,f){let g=f?this.ctx:this.map;if((!a[b]||f&&!a[b][f])&&(this.optimize&&(g=g[c]),f?(a=a[b]||(a[b]=create_object()),a[f]=1,g=g[f]||(g[f]=create_object())):a[b]=1,g=g[b]||(g[b]=[]),this.optimize||(g=g[c]||(g[c]=[])),(!e||!g.includes(d))&&(g[g.length]=d,this.fastupdate))){const a=this.register[d]||(this.register[d]=[]);a[a.length]=g}},Index.prototype.search=function(a,b,c){c||(!b&&is_object(a)?(c=a,a=c.query):is_object(b)&&(c=b));let d,e,f,g=[],h=0;if(c&&(a=c.query||a,b=c.limit,h=c.offset||0,e=c.context,f=SUPPORT_SUGGESTION&&c.suggest),a&&(a=this.encode(""+a),d=f(!e){e=Math.min(a.length,c);for(let g,h=0;h<e;h++)g=a[h],g&&(f=remove_index(g,b,c,d,e),!d&&!f&&delete a[h])}else{const c=a.indexOf(b);-1===c?f++:1<a.length&&(a.splice(c,1),f++)}return f}SUPPORT_CACHE&&(Index.prototype.searchCache=searchCache),SUPPORT_SERIALIZE&&(Index.prototype.export=exportIndex,Index.prototype.import=importIndex),SUPPORT_ASYNC&&apply_async(Index.prototype);^^^^^^

emersonbottero commented 2 years ago

If I add type: module manuanly in the package.json inside node modules it does work.

{
  "name": "flexsearch",
  "version": "0.7.31",
  "type": "module",
  ...
}
schalkneethling commented 2 years ago

Not sure if this answers your questions, but this is how I import for example Document in an ESM project:

import pkg from "flexsearch";
const { Document } = pkg;
emersonbottero commented 2 years ago

it also brakes when I try to build.

davidhq commented 1 year ago

@ts-thomas would replacing google-closure-compiler with something else potentially solve the problem?

If you think yes, I can look into how to do it... I had some other problem with google-closure-compiler not being able to compile because of some java version mismatch on OSX...

so if replacing this with something else would solve two issues at once, I'm willing to invest some time,let me know

davidhq commented 1 year ago

maybe some more modern tool can replace both webpack and google-closure-compiler ...

also is Babel needed at all? It is also obsolete, I believe

productdevbook commented 1 year ago

It doesn't work for me either, I couldn't succeed somehow

Akryum commented 1 year ago

https://github.com/Akryum/flexsearch-es

rileyjshaw commented 1 year ago

https://github.com/Akryum/flexsearch-es

Thanks for publishing this! @ts-thomas, do you think any of @Akryum’s changes could find their way upstream?

Tasin5541 commented 1 year ago

https://github.com/Akryum/flexsearch-es

I am getting FlexSearch.create is not a function error on vite

const searchIndex = FlexSearch.create({
  encode: "icase",
  tokenize: "forward",
  cache: true,
  doc: {
    id: "path",
    field: ["name", "content"],
  },
});
Akryum commented 1 year ago

@Tasin5541 The code doesn't export a create function (the documentation and types seem to be wrong).

danawoodman commented 1 year ago

@ts-thomas do you plan to support ESM? At this point, it seems the package isn't usable with most of the modern web frameworks if ESM is not a supported environment, unless I'm missing something.

EDIT: Apparently the following imports do work, at least in a Vite + Svelte project I'm working on:

  import Index from "flexsearch/dist/module";
  import Worker from "flexsearch/dist/module/worker";
  import Document from "flexsearch/dist/module/document";

EDIT 2: I've created a PR #398 that updates the docs to explain how to import the ESM modules.

PlayXman commented 1 year ago

I had a hard time to make it running in a Typescript project, but importing the modules from src surprisingly worked:

import Document from "flexsearch/src/document";

interface IndexedDocument {
  id: string;
  firstName: string;
  lastName: string;
}

const docIndex = new Document<IndexedDocument, true>({
  document: {
    id: "id",
    index: [ "firstName", "lastName" ],
    store: true,
  },
});